Usa una instancia o una clase para obtener recursos del juego (madera, hierro, oro)

31

Así que estoy haciendo un juego donde puedes enviar barcos a lugares para vender o comprar recursos como madera, hierro, oro, etc.

Ahora me preguntaba cómo deberían crearse los recursos en el juego. Se me ocurrieron 2 opciones

  1. Crea una clase para cada recurso:

    public class ResourceBase {
        private int value;
        // other base properties
    }
    
    public class Gold : ResourceBase {
         public Gold {
             this.value = 40 // or whatever
         }
    }
  2. Crear instancias de una clase de recurso

    public class Resource {
        string name;
        int value;
    
        public Resource(string name, int value) {
            this.name = name;
            this.value = value;
         }
     }
    
    // later on...
    
    Resource gold = new Resource("Gold",40);

Con la segunda opción es posible llenar los recursos del juego desde un archivo resources.json, me gusta un poco esa estructura.

¡Nuevas ideas / patrones de diseño / estructuras son siempre bienvenidas!

EDITAR: es un poco como la aplicación complementaria de Assassins Creed Black Flag. Vea la barra de recursos en la imagen a continuación

EDIT 2: He investigado un poco más sobre "cargar elementos / recursos del archivo JSON" y encontré este blog: Poder de JSON en el desarrollo de juegos - Elementos . Muestra lo mejor de la opción 1 y la opción 2. Aún puede agregar funcionalidad a cada recurso :)

ingrese la descripción de la imagen aquí

MDijkstra
fuente
1
Minecraft tiene clases separadas para cada recurso (no es lo que debería)
MCMastery
@MCMastery ohh es minecraft de código abierto? Me gustaría echar un vistazo a esa estructura. Y gracias por mencionar
MDijkstra
1
No lo es, pero siempre se está ofuscado-de los servidores ... ver github.com/Wolf-in-a-bukkit/Craftbukkit/tree/master/src/main/...
MCMastery
12
No uses una cuerda como esa. Es muy fácil escribir mal "oro" con "glod" o similar, y es un gran dolor depurarlo. Use un enumen su lugar.
Nolonar
Minecraft no es técnicamente de código abierto, pero se ha descompilado y desobuscado casi por completo. No debería poder encontrar una fuente desofuscada en ningún lugar de Internet, pero puede descargar y ejecutar el proceso para hacerlo desde files.minecraftforge.net (la descarga de MDK es la que desea)
JamEngulfer

Respuestas:

51

Vaya con el segundo enfoque, simplemente debido al hecho de que puede introducir nuevos tipos de recursos o elementos en cualquier momento sin tener que reescribir o actualizar el código ( desarrollo basado en datos ).


Editar:

Para elaborar un poco más sobre por qué esto es una buena práctica en general, incluso si está 100% seguro de que algún valor nunca cambiará.

Tomemos el ejemplo del juego de consola mencionado en los comentarios, porque proporciona una muy buena razón por la que no debe codificar nada, a menos que realmente tenga (o quiera), por ejemplo, claves de cifrado, su propio nombre, etc.

Cuando se lanza un juego en consolas, estos generalmente tienen que pasar por un proceso de revisión, donde el control de calidad del fabricante de la consola probará el juego, lo jugará, buscará problemas, etc. Esto es obligatorio y cuesta dinero, mucho dinero. Creo que una vez leí que un lanzamiento podría costar entre 30,000 y 50,000 dólares solo por la certificación.

Ahora imagine que está presionando su juego para su lanzamiento, pagando los $ 30,000 y espere. Y de repente te das cuenta de que algunos de los valores de tu juego están literalmente rotos. Digamos que puedes comprar barras de hierro por 50 de oro y venderlas por 55 de oro.

¿Qué haces? Si ha codificado esas cosas, tendrá que crear una nueva versión de actualización / lanzamiento, que tendrá que pasar por una revisión una vez más, ¡así que el peor de los casos pagará una vez más por la nueva certificación! Apuesto a que a Ubisoft no le importará, pero para tu propio bolsillo de desarrollador de juegos independientes ... ¡ay!

Pero imagine que el juego solo verificaría ocasionalmente las definiciones actualizadas del juego (por ejemplo, en forma de un archivo JSON). Tu juego podría descargar y usar la última versión en cualquier momento sin requerir una nueva actualización. Puede corregir ese desequilibrio / explotación / error en cualquier momento sin requerir una nueva certificación o cualquier otro dinero pagado. Solo una pequeña decisión de diseño, no creías que valiera la pena, ¡solo te ahorró una suma de dinero de 5 dígitos! ¿No es asombroso? :)

Solo no me malinterpretes. Esto también se aplica a otros tipos de software y plataformas. La descarga de archivos de datos actualizados es en general mucho más fácil y factible para el usuario estándar, sin importar si es un juego o algún tipo de aplicación / herramienta. Imagine un juego instalado bajo Archivos de programa en Windows. Su actualizador necesita derechos de administrador para modificar cualquier cosa y no puede modificar los programas en ejecución. Con DDD, su programa simplemente descarga los datos y los usa. Es posible que el jugador ni siquiera note que ha habido una actualización.

Mario
fuente
66
+1 para DDD. Esto es muy apropiado para cosas como los recursos.
Kromster dice que apoya a Monica el
@Funnydutchman si la respuesta de alguien te ayudó, en Stack Exchange se acostumbra votar su respuesta haciendo clic en la flecha sobre el número a la izquierda. Si una respuesta lo ayudó más, debe hacer clic en la marca de verificación debajo de la flecha inferior para aceptar su respuesta. Ambas acciones le dan al respondedor una bonificación en reputación y hacen que las respuestas utilizables sean más visibles para la comunidad.
Nzall
2
No veo cómo tendrías que reescribir el código de la primera manera; todo lo que harías es agregar una clase (a menos que me falte algo).
SirPython
44
Y aquí está la diferencia entre el código orientado a objetos y el código obsesionado con objetos. El hecho de que pueda crear una jerarquía de objetos no significa necesariamente que deba hacerlo.
corsiKa
2
@ AgustínLado Si tuviera un dólar por cada vez que un cliente dijera "Esto nunca cambiará" y cambió, podría llevarnos a ambos a un restaurante decente para cenar (aunque tendríamos que escatimar un poco en la propina, así que tal vez solo un restaurante mediocre ... todavía hay suficientes horas para cenar para dos.)
corsiKa
29

Una regla general es que use diferentes clases cuando los objetos requieren diferentes códigos e instancias de la misma clase cuando los objetos solo requieren diferentes valores.

Cuando los recursos tienen diferentes mecánicas de juego que son únicas para ellos, puede tener sentido representarlos con clases. Por ejemplo, cuando tienes Plutonio que tiene una vida media y conejos que procrean de acuerdo con la secuencia de Fibonacci , entonces podría tener sentido tener una clase base Resourcecon un método Updateimplementado como no operativo y dos clases derivadas HalfLifeResourcey SelfGrowingResourceque anule ese método para disminuir / aumentar el valor (y tal vez también incluya lógica para controlar lo que sucede cuando almacena ambos uno al lado del otro).

Pero cuando sus recursos son realmente solo números tontos, entonces no tiene sentido implementarlos con algo más elegante que una simple variable.

Philipp
fuente
Gracias @Phillipp por tu respuesta. Esto es lo que estaba pensando. Pero los recursos realmente no cambiarán en términos de propiedades / métodos, así que iré con la opción 2 por ahora
MDijkstra
14

Me gustaría agregar que hay dos opciones adicionales:
Interfaz: puede considerar si la clase de recurso sería solo un "almacenamiento" para 5 enteros y cada entidad tendría lógica diferente con respecto a los recursos (por ejemplo, gasto / botín del jugador, producción de la ciudad recurso). En ese caso, es posible que no desee una clase en absoluto; simplemente quería exponer que una entidad tiene una variable con la que otras pueden interactuar:

interface IHasResources {
  int Gold { get; set; }
  int Wood { get; set; }
  int Cloth { get; set; }
  // ....
}

class Player : IHasResources { }
class City: IHasResources { }

Además, esto tiene la ventaja de que puedes saquear una ciudad con exactamente el mismo código que saquearías la flota enemiga, promoviendo la reutilización del código.
La desventaja es que implementar dicha interfaz puede resultar tedioso si muchas clases lo implementan, por lo que podría terminar usando interfaces pero no para el problema original, resolviéndolo con la opción 1 o 2:

interface IHasResources { 
   IEnumerable<Resource> Resources { get; }
}

Instancias (con enum): en algunos casos, sería deseable usar enum en lugar de cadena al definir el tipo de recurso:

enum Resource {
   Gold, Wood, Cloth, //...
}

class ResourceStorage {
   public Resource ResourceType { get; set; }
   public int Value { get; set; }
}

esto proporcionará un poco de "seguridad", porque su IDE le dará una lista de todos los recursos válidos cuando lo asigne después de escribir el punto ResourceType = Resource., además hay más "trucos" con enumeraciones que podría necesitar, como Tipo explícito o composición / banderas que puedo imaginar que sea útil al elaborar:

[Flags]
enum Metal {
 Copper = 0x01/*01*/, Tin = 0x02/*10*/, Bronze = 0x03/*11*/
}
Metal alloy = Metal.Copper; //lets forget Value(s) for sake of simplicity
alloy |= Metal.Tin; //now add a bit of tin
Console.WriteLine(alloy); //will print "Bronze"


En cuanto a lo que es mejor: no hay una bala de plata, pero personalmente me inclinaría por cualquier forma de casos, puede que no sean tan elegantes como la interfaz, pero son simples, no abarrotan el árbol de herencia y son ampliamente entendidos.

wondra
fuente
2
Gracias por tu respuesta @wondra Me gusta la opción de enumeración y cómo hiciste ese bronce con cobre y estaño; p
MDijkstra
2
Me uní a esta comunidad específicamente para poder votar la solución enum. También puede considerar el uso del atributo Descripción enum para asignar desde alguna representación JSON de la enumeración ( my-great-enum) a alguna representación C # de la enumeración ( MyGreatEnum).
Dan Forbes
Aparentemente he estado fuera del bucle de Java demasiado tiempo. ¿Desde cuándo está permitido tener declaraciones de campo para interfaces? Hasta donde yo sé, estos son automáticamente estáticos y finales, y por lo tanto no son muy útiles para los fines descritos en la pregunta.
M.Herzkamp
2
@ M.Herzkamp no es un campo, es una propiedad, y Java no admite propiedades. La pregunta está etiquetada como C #, C # permite propiedades en las interfaces; después de todo, las propiedades son métodos subyacentes. Me temo que lo mismo ocurre con la anotación de banderas, no compatible con Java, aunque no estoy completamente seguro.
wondra
¡Gracias por aclarar eso! Me perdí la etiqueta C # y el código en la pregunta se parecía muchísimo a Java: D
M.Herzkamp
2

Debe usar la opción 1, que tiene 3 ventajas:

  1. Es más fácil crear instancias de un recurso: en lugar de tener que pasar el tipo de recurso como parámetro, simplemente haría, por ejemplo, new Gold(50)
  2. Si necesita dar un comportamiento especial a un recurso, puede hacerlo cambiando su clase.
  3. Es más fácil verificar si un recurso es de cierto tipo: resource instanceof Gold
Zac
fuente
Todas las respuestas tienen sus pros y sus contras, es difícil elegir cuál es mejor. He hecho una edición a la pregunta, ¿qué opinas de esa estructura?
MDijkstra
1

Si sus recursos no tienen lógica de negocios propia, o escenarios de casos especiales con cómo interactúan con el entorno, otros recursos (además del comercio que ha cubierto) o el jugador, la opción dos hace que agregar nuevos sea muy fácil.

Si necesita considerar situaciones como lo que sucedería si combina dos recursos para la elaboración, si los recursos pueden tener propiedades muy diferentes (un balde de agua es muy diferente a un trozo de carbón) u otras interacciones económicas complejas, entonces el enfoque 1 es mucho más flexible para el motor, pero no para los datos.

Kristian Williams
fuente