Diseño del proyecto - RangelCham73/Adventure-mod-1.21.4 GitHub Wiki
Estructura
Es importante seguir la estructura del proyecto para una mayor organización y la estructura de recursos es estrictamente necesaria seguir ya que minecraft recoge de esa manera especifica los modelos y las texturas.
src/
├── com/rangelcham/adventuremod/
| ├── custom/ # Funcionalidades de minecraft añadidas
| | ├── block/ # Bloques nuevos
| | ├── commands/ # Comandos personalizados
| | ├── effect/ # Efectos nuevos
| | └── item/ # Objetos nuevos
| ├── nbt/ # Sistema de carga/guardado de datos mediante archivos nbt
| ├── player/ # Personaje del jugador
| | ├── abilities/ # Habilidades ya sean de metroidvania o de las ramas de habilidades
| | ├── stats/ # Atributos del jugador
| ├── quests/ # Sistema de misiones
└── resources/assets/adventuremod/
├── blockstates/ # Archivos json para poder representar los objetos de los bloques
├── items/ # Modelos json de los objetos de los bloques
├── lang/ # Lugar de los idiomas del mod
├── models/ # Modelos 3d
| ├── block/ # Modelos de los bloques
| └── item/ # Modelos de los objetos
└── textures/ # Texturas png de los objetos
├── block/ # Texturas de los bloques
└── item/ # Texturas de los objetos
Implementación
Seguimiento detallado de la implementación que se tiene que llevar a cabo para la construcción de la aplicación
Tratamiento del fichero NBT
La Named Binary Tag ( NBT ) es una estructura de datos de árbol que Minecraft utiliza en muchos archivos de guardado para almacenar datos arbitrarios. El formato se compone de varias etiquetas. Estas etiquetas tienen un ID numérico, un nombre y una carga útil.
Si quieres entrar en detalle la wiki de Minecraft ofrece información suficiente para construir NBTs: Wiki de Minecraft.
Nosotros aprovechamos estos datos para guardar datos simples de nuestros objetos y poder guardarlos y cargarlos al jugador. Vamos a tener un Handler
por cada sección (Misiones, Jugador y Habilidades) que se encargará de guardar y cargar los datos cuando nosotros queramos. Aqui un pequeño ejemplo para el Handler de Habilidades incompleto:
Ejemplo de carga
public static void loadAbilities(Player player) {
CompoundTag tag = player.getPersistentData();
CompoundTag abilityTag = tag.getCompound(ABILITIES_KEY);
if (abilityTag.contains(DOUBLE_JUMP_KEY)) {
boolean doubleJump = abilityTag.getBoolean(DOUBLE_JUMP_KEY);
if (doubleJump) {
DoubleJumpHandler.unlockedDoubleJump = true;
}
}
if (abilityTag.contains(DASH_KEY)) {
boolean dash = abilityTag.getBoolean(DASH_KEY);
if (dash) {
DashHandler.unlockedDash = true;
}
}
}
Ejemplo de guardado
public static void saveAbilities(Player player) {
CompoundTag tag = player.getPersistentData();
CompoundTag abilityTag = new CompoundTag();
abilityTag.putBoolean(DOUBLE_JUMP_KEY, DoubleJumpHandler.unlockedDoubleJump);
abilityTag.putBoolean(DASH_KEY, DashHandler.unlockedDash);
tag.put(ABILITIES_KEY, abilityTag);
player.getPersistentData().put(ABILITIES_KEY, abilityTag);
}
Aparte de esto tenemos un Handler
maestro que se encarga de cargar los datos cuando se inica el mundo, gracias al evento onPlayerJoin
y cuando el jugador sale del mundo guarda todos los datos, gracias al evento onPlayerLeave
: PlayerEventHandler
Cabe destacar que al ser un mapa que se juega de manera individual no hace falta diferenciar entre ServerPlayer
y LocalPlayer
. Trataremos al jugador de Minecraft con su clase genérica Player
Personalización de Minecraft
NeoForged permite crear todo tipo de personalización de cosas a Minecraft Vanila. Para ello hay buenos videos bastante cortos que lo explican a la perfección todo esto. Video de YT
Bloques
Mediante el uso del objeto facilitado por NeoForged, DeferredBlock
, se puede crear cualquier bloque que necesites, con las propiedades que especifiques. Todos los bloques se crearan en la clase ModBlocks
que será la que se registre en el bus de eventos.
Objetos
Mediante el uso del objeto facilitado por NeoForged, DeferredItem
, se puede crear cualquier objeto que necesites, con las propiedades que especifiques. Todos los objetos se crearan en la clase ModItems
que será la que se registre en el bus de eventos.
Efectos
Mediante el uso del objeto facilitado por NeoForged, DeferredRegister
, se puede crear cualquier efecto que necesites, con las propiedades que especifiques. Todos los efectos se crearan en clases propias para poder especificar los numerosos efectos que tengamos. Se hace de esta manera ya que nosotros utilizamos los efectos para modificar directamente a nuestro jugador y cada efecto utiliza partes de la memoria diferente. Igualmente todas las clases de efectos se registraran en la clase ModEffects
y esta se registrará en el bus de eventos
Comandos
Para los comandos es un poco más complicado ya que tenemos que crear una clase por cada comando e implementar toda la funcionalidad en esa clase. Y para registrar el comando se hará en la clase anteriormente mencionada PlayerEventHandler
, gracias al evento onRegisterCommands
.
Sistemas
Para este tipo de implementaciones seguiremos la siguiente estructura de clases:
Screen
: Clase que se encarga de la interfaz de usuario y su diseño.KeyBind
: Clase que define la tecla para ejecutar habilidad/abrir menu.Object
: Donde se guarde la información que queremos utilizar.Handler
: Se encarga de implementar toda la funcionalidad.
Atributos del jugador
Para el caso de los atributos:
StatsScreen
: (Por Diseñar)StatsKeyBind
: Se ha elegido la tecla K, pero el usuario la puede cambiar a cualquiera.PlayerStats
: Objeto donde se guarda toda la informaciónStatsHandler
: Encargado de implementar la funcionalidad de la keyBind y todo lo relacionado con los atributos del jugador y el jugador en si.
Habilidades Metroidvania
Para el caso de estas habilidades, es un pelín diferente, no vamos a necesitar interfaz de usuario, ni objeto para guardar datos. Tenemos el Handler
de cada habilidad.
- Dash:
DashKeyBind
: Para definir la tecla del dash, ALT IZQ. Igualmente el usuario lo puede cambiar en los controles del juego.DashHandler
: Implementa el dash, gracias a cálculos vectoriales. Ser ha puesto una espera de 2 segundos para no abusar del dash de manera continua.
- Doble salto:
DoubleJumpHandler
: Como la tecla seria el ESPACIO no seria necesario utilizar un KeyBind porque esa tecla seria la de saltar normalmente, si el usuario cambia la tecla de saltar en el juego se cambiaria también esta de manera automática. Igualmente se hacen cálculos vectoriales para realizar el salto. Cabe destacar que al hacer un salto en el aire niega el daño de caída acumulado hasta el momento, no nulifica todo el daño de caída.
Es importante decir que lo único que vamos a guardar en la NBT sobre esto es un booleano por cada habilidad, este booleano marcara si la habilidad esta desbloqueada o no. El resto de información es constante y no va a cambiar.
Ramas de habilidades
(Por Diseñar)
Guerrero
(Por Diseñar)
Cristalizador
(Por Diseñar)
Guardian
(Por Diseñar)
Misiones
Para el caso de los atributos:
QuestScreen
: (Por Diseñar)QuestKeyBind
: Se ha elegido la tecla J, pero el usuario la puede cambiar a cualquiera.Quest
: Objeto donde se guarda toda la informaciónQuestsHandler
: Encargado de implementar la funcionalidad de la keyBind y todo lo relacionado con las misiones.
Es importante saber como se ha implementado la funcionalidad de las misiones. Gracias al comando QuestCommand
podemos mandar una señal a nuestro programa desde un bloque de comandos en el mundo. En este comando especificamos un id de misión y un id de etapa. Las misiones se van a guardar en un mapa <Integer, Quest>
teniendo así un id para identificar la misión.
Si al comando le pasamos en la etapa un 0 marcaremos que se habilite en el diario de misiones del jugador la misión y así pueda verla y completarla.
La información que guardamos en el NBT son solo 3 booleanos por cada misión: si esta habilitada, si esta completada y la etapa actual. El resto de información es constante y no va a cambiar.
Diseño de texturas y modelos
Es importante seguir la siguiente estructura porque todos los archivos JSON del Minecraft se leen de esa manera si o si. Los nombres de los ficheros JSON tienen que tener el mismo nombre que pongas en la clase ModBlocks o ModItems.
Blockstates
{
"variants": {
"": {
"model": "adventuremod:block/(nombre del id en el objeto ModBlocks)"
}
}
}
Items
{
"model": {
"type": "minecraft:model",
"model": "adventuremod:item/(nombre del id en el objeto ModItems)"
}
}
Lang
El nombre del json tiene que seguir un identificador segun el idioma. Por ejemplo español es es_Es o ingles es en_US.
{
"item.adventuremod.(id del objeto en el objeto ModItems)": "texto",
"key.categories.adventuremod": "Mod"
}
Models/Blocks
{
"parent": "minecraft:block/cube_all",
"textures": {
"all": "adventuremod:block/(id del objeto en el objeto ModBlocks)"
}
}
Models/Items
En caso de bloque
{
"parent": "adventuremod:block/(id del objeto en el objeto ModBlocks)"
}
En caso de objeto
{
"parent": "minecraft:item/generated",
"textures": {
"layer0": "adventuremod:item/(id del objeto en el objeto ModItems)"
}
}