showmessagedialog - mostrar mensaje de error en java
Estructuras de datos para pasar mensajes dentro de un programa? (4)
Estoy tratando de escribir un juego de rol simple. Hasta ahora, cada vez que trato de comenzar, instantáneamente se convierte en un desastre y no sé cómo organizar nada. Así que estoy empezando de nuevo, tratando de prototipar una nueva estructura que es básicamente el marco MVC. Mi aplicación comienza la ejecución en el Controlador, donde creará la Vista y el Modelo. Luego ingresará al bucle del juego, y el primer paso en el bucle del juego es recopilar la información del usuario.
La entrada del usuario será recopilada por una parte de la vista, porque puede variar (una vista 3D sondeará directamente la entrada del usuario, mientras que tal vez una vista remota la recibirá a través de una conexión telnet, o una vista de línea de comando usaría System.in ) La entrada se traducirá en mensajes, y cada mensaje se entregará al controlador (mediante una llamada a un método) que luego podrá interpretar el mensaje para modificar los datos del modelo o enviar datos a través de la red (ya que espero tener una opción de red). .
Esta técnica de manejo de mensajes también se puede usar, en el caso de un juego en red, para procesar mensajes de red. ¿Estoy manteniendo el espíritu del MVC hasta ahora?
De todos modos, mi pregunta es, ¿cuál es la mejor forma de representar estos mensajes?
Aquí hay un caso de uso, con cada mensaje en cursiva: digamos que el usuario inicia el juego y elige el carácter 2 . Luego el usuario se mueve a las coordenadas (5,2) . Luego dice a la charla pública, "¡hola!" . Luego elige guardar y renunciar .
¿Cómo debería la vista resumir estos mensajes en algo que el controlador pueda entender? ¿O crees que debería tener métodos de control separados como chooseCharacter (), moveCharacterTo (), publicChat ()? No estoy seguro de que una implementación tan simple funcione cuando me cambio a un juego en red. Pero en el otro extremo del extremo, no quiero simplemente enviar cadenas al controlador. Es difícil porque la acción de elegir el personaje toma un entero, el movimiento toma dos enteros, y el chat toma una cadena (y un ámbito (público privado global) y en el caso de privado, un usuario de destino); no hay ningún tipo de datos reales para todo.
También cualquier sugerencia general es muy bienvenida; ¿Me estoy preocupando por esto en el momento correcto? ¿Me dirijo por el camino correcto a una aplicación MVC bien diseñada? ¿Hay algo que he olvidado?
¡Gracias!
Aunque no estoy completamente convencido de que MVC se preste bien al diseño de juegos, hay algunos artículos que explican los aspectos básicos de dónde poner diferentes partes de la lógica del juego usando una arquitectura MVC. Aquí hay una referencia rápida que responde bastantes de sus preguntas:
Haga que la mía sea otra respuesta para "MVC considerado potencialmente dañino en los juegos". Si su representación 3D es ''una vista'' y su tráfico de red es ''una vista'', ¿entonces no termina con clientes remotos que terminan tratando una vista como modelo? (El tráfico de red puede parecer solo otro mecanismo de visualización cuando lo envía, pero en el extremo receptor es su modelo definitivo en el que se basa su juego). Mantenga MVC en el lugar al que pertenece: separación de la presentación visual de la lógica.
En general, desea trabajar enviando un mensaje al servidor y esperando hasta obtener una respuesta. Si ese servidor está en otro continente o dentro del mismo proceso no importa si lo maneja de la misma manera.
Digamos que el usuario inicia el juego y elige el carácter 2. Luego el usuario se mueve a las coordenadas (5,2). Luego dice a la charla pública, "¡hola!". Luego elige guardar y renunciar.
Mantenlo simple. Los MUD solían simplemente enviar el comando en texto plano (por ejemplo, "SELECCIONAR carácter2", "MOVER A 5,2", "DIGA Hola") y hay pocas razones por las que no podrías hacer eso, si te sientes cómodo escribiendo el comando analizador de texto.
Una alternativa más estructurada sería enviar un objeto XML simple, ya que sé que los chicos de Java adoran XML;)
<message>
<choose-character number=''2''/>
</message>
<message>
<move-character x=''5'' y=''2''/>
</message>
<!--- etc --->
En los juegos comerciales, tendemos a tener una estructura binaria que contiene un identificador de tipo de mensaje y luego una carga útil arbitraria, con serialización para empaquetar y descomprimir dichos mensajes en cada extremo. Sin embargo, no necesitarías ese tipo de eficiencia aquí.
No estoy tan seguro de que un framework MVC sea correcto para un juego, pero supongo que estás creando un servidor de juegos para, por ejemplo, un MUD o un simple MMPROGOOGPRG, y que la legibilidad y actualización del código es más importante para ti que la cruda. actuación.
Depende de la cantidad de usuarios que desee admitir al mismo tiempo y las capacidades de su servidor de juegos. Podría comenzar con una E / S basada en texto, y luego pasar a una representación binaria o XML a medida que su proyecto madure.
Ciertamente tendré diferentes acciones, con una clase diferente haciendo cada comando posible.
Su analizador de interfaz de usuario crearía objetos UserAction (en realidad, subclases, T extiende UserAction) desde la capa de red / vista-> controlador. Esto le permite cambiar la forma en que su red opera más adelante sin separar su aplicación central. Probablemente ya esté pensando que podría usar serialización personalizada o similar para los mensajes con estos objetos UserAction. Esta UserAction pasará a su implementación de UserActionHandler (Command) a través de una Factory o simplemente revisará un campo CommandEnum dentro de un switch. Dicho Manejador entonces hará la magia necesaria en el modelo, y el controlador notará los cambios de estado del modelo y enviará notificaciones a otros jugadores / vistas, y así sucesivamente.
(Descargo de responsabilidad: nunca programé juegos en Java, solo en C ++. Pero la idea general debería ser aplicable también en Java. Las ideas que presento no son mías, sino una mezcla de soluciones que encontré en libros o "en Internet". ", vea la sección de referencias. Yo mismo empleo todo esto y hasta ahora resulta en un diseño limpio donde sé exactamente dónde poner nuevas características que agrego).
Me temo que esta será una respuesta larga, puede que no esté clara al leer por primera vez, ya que no puedo describirla simplemente de arriba hacia abajo, así que habrá referencias de un lado a otro, esto se debe a mi sin habilidad explicativa, no porque el diseño sea defectuoso. En retrospectiva, me extralimité e incluso puedo estar fuera del tema. Pero ahora que he escrito todo esto, no puedo obligarme a tirarlo. Solo pregunta si algo no está claro.
Antes de comenzar a diseñar cualquiera de los paquetes y clases, comience con un análisis. ¿Cuáles son las características que quieres tener en el juego? No planee un "quizás agregue esto más tarde", porque casi con certeza las decisiones de diseño que usted toma por adelantado antes de comenzar a agregar esta característica en serio, el talón que planeó para él será insuficiente.
Y por motivación, hablo por experiencia aquí, ¡no pienses en tu tarea como escribir un motor de juego, escribir un juego! Independientemente de lo que piense sobre lo que sería genial tener para un proyecto futuro, rechace a menos que lo ponga en el juego que está escribiendo en este momento. Sin código muerto no probado, sin problemas de motivación debido a que no se puede resolver un problema que ni siquiera es un problema para el proyecto inmediato. No hay un diseño perfecto, pero hay uno lo suficientemente bueno. Vale la pena tener esto en cuenta.
Como dije antes, no creo que MVC sea útil al diseñar un juego. La separación de modelo / vista no es un problema, y las cosas del controlador son bastante complicadas, demasiado para llamarse simplemente "controlador". Si desea tener subpaquetes denominados modelo, vista, control, continúe. Los siguientes pueden integrarse en este esquema de empaquetado, aunque otros son al menos tan sensatos.
Es difícil encontrar un punto de partida en mi solución, así que simplemente empiezo por lo más alto:
En el programa principal, simplemente creo el objeto Aplicación, lo inicio y lo inicio. La aplicación init()
creará los servidores de características (ver abajo) y los inserta. También se crea el primer estado del juego y se empuja hacia arriba. (también ver abajo)
Los servidores de funciones encapsulan características de juego ortogonales. Estos pueden implementarse de forma independiente y se acoplan de forma flexible mediante mensajes. Funciones de ejemplo: sonido, representación visual, detección de colisión, inteligencia artificial / toma de decisiones, física, etc. Cómo se organizan las características se describe a continuación.
Entrada, control de flujo y el bucle del juego
Los estados del juego presentan una forma de organizar el control de entrada. Normalmente tengo una sola clase que recopila eventos de entrada o captura el estado de entrada y lo consulta más tarde (InputServer / InputManager). Si se usa el enfoque basado en eventos, los eventos se otorgan al único estado de juego activo registrado.
Al comenzar el juego, este será el estado del juego del menú principal. Un estado de juego tiene funciones de init/destroy
y resume/suspend
. Init()
inicializará el estado del juego, en el caso del menú principal, mostrará el nivel superior del menú. Resume()
dará control a este estado, ahora toma la entrada del InputServer. Suspend()
borrará la vista del menú de la pantalla y destroy()
liberará todos los recursos que el menú principal necesite.
Los GameStates se pueden apilar, cuando un usuario inicia el juego con la opción "nuevo juego", el estado del juego MainMenu se suspende y PlayerControlGameState se coloca en la pila y ahora recibe los eventos de entrada. De esta forma puedes manejar la entrada dependiendo del estado de tu juego. Con solo un controlador activo en un momento dado, simplifica enormemente el flujo de control.
La colección de entrada es activada por el bucle del juego. El bucle del juego básicamente determina el tiempo de trama para el bucle actual, actualiza los servidores de funciones, recopila entradas y actualiza el estado del juego. El tiempo de trama se da a una función de actualización de cada uno de estos o es proporcionado por un temporizador individual. Este es el tiempo canónico utilizado para determinar la duración del tiempo desde la última llamada de actualización.
Objetos del juego y características
El corazón de este diseño es la interacción de objetos y características del juego. Como se muestra arriba, una característica en este sentido es una función del juego que se puede implementar de forma independiente. Un objeto de juego es cualquier cosa que interactúa con el jugador o cualquier otro objeto del juego de alguna manera. Ejemplos: el avatar del jugador en sí es un objeto del juego. Una antorcha es un objeto de juego, los PNJ son objetos de juego, como zonas de iluminación y fuentes de sonido o cualquier combinación de estos.
Tradicionalmente los objetos de juego RPG son la clase superior de alguna jerarquía sofisticada de clases, pero realmente este enfoque es simplemente incorrecto. Muchos aspectos ortogonales no se pueden poner en una jerarquía e, incluso utilizando interfaces, al final se deben tener clases concretas. Un objeto es un objeto de juego, un objeto que se puede recoger es un objeto de juego, un cofre es un recipiente es un artículo, pero hacer un cofre que pueda o no es una decisión cualquiera con este enfoque, ya que tienes que tener un solo objeto jerarquía. Y se vuelve más complicado cuando quieres tener un cofre de acertijo mágico que habla y que solo se abre cuando se responde a un acertijo. Simplemente no hay una jerarquía que se ajuste a todos.
Un mejor enfoque es tener solo una clase de objeto de juego y poner cada aspecto ortogonal, que normalmente se expresa en la jerarquía de clases, en su propia clase de componente / característica. ¿Puede el objeto del juego contener otros objetos? A continuación, agregue la función ContainerFeature a ella, puede hablar, agregue TalkTargetFeature y así sucesivamente.
En mi diseño, un GameObject solo tiene una propiedad de identificación, nombre y ubicación intrínseca única, todo lo demás se agrega como un componente de función. Los componentes se pueden agregar en tiempo de ejecución a través de la interfaz de GameObject llamando a addComponent (), removeComponent (). Para que sea visible, agregue un VisibleComponent, haga que emita sonidos, agregue un AudableComponent, conviértalo en un contenedor, agregue un ContainerComponent.
VisibleComponent es importante para su pregunta, ya que esta es la clase que proporciona el vínculo entre el modelo y la vista. No todo necesita una vista en el sentido clásico. Una zona de disparo no será visible, una zona de sonido ambiental tampoco lo hará. Solo los objetos del juego que tengan VisibleComponent estarán visibles. La representación visual se actualiza en el ciclo principal, cuando se actualiza el VisibleFeatureServer. Luego actualiza la vista de acuerdo con los componentes VisibleComponents registrados en ella. Si consulta el estado de cada uno o simplemente pone en cola los mensajes recibidos de ellos depende de su aplicación y de la biblioteca de visualización subyacente.
En mi caso, uso Ogre3D. Aquí, cuando se asocia un VisibleComponent a un objeto de juego, se crea un SceneNode adjunto al gráfico de escena y al nodo de escena una Entity (representación de una malla 3d). Cada TransformMessage (ver más abajo) se procesa inmediatamente. El VisibleFeatureServer luego hace que Ogre3d vuelva a dibujar la escena en RenderWindow (En esencia, los detalles son más complicados, como siempre)
Mensajes
Entonces, ¿cómo se comunican estas características y los estados del juego y los objetos del juego entre sí? A través de mensajes. Un mensaje en este diseño es simplemente cualquier subclase de la clase Mensaje. Cada Mensaje concreto puede tener su propia interfaz que sea conveniente para su tarea.
Los mensajes se pueden enviar de un GameObject a otro GameObjects, de un GameObject a sus componentes y de FeatureServers a los componentes de los que son responsables.
Cuando se crea un FeatureComponent y se agrega a un objeto del juego, se registra en el objeto del juego llamando a myGameObject.registerMessageHandler (this, MessageID) para cada mensaje que desea recibir. También se registra en su servidor de características para cada mensaje que desea recibir desde allí.
Si el jugador intenta hablar con un personaje que tiene en su foco, entonces el usuario activará de alguna manera la acción de hablar. Por ejemplo: si el char en foco es un NPC amistoso, al presionar el botón del mouse se activa la interacción estándar. La acción estándar de objetos del juego objetivo se consulta enviando un GetStandardActionMessage. El objeto objetivo del juego recibe el mensaje y, comenzando con el primero registrado, notifica a sus componentes de características que desean saber sobre el mensaje. El primer componente de este mensaje establecerá la acción estándar a la que se desencadenará (TalkTargetComponent establecerá la acción estándar en Talk, que también recibirá en primer lugar) y luego marcará el mensaje como consumido. GameObject probará el consumo y verá que efectivamente se consume y regresa a la persona que llama. El mensaje ahora modificado se evalúa y se invoca la acción resultante
Sí, este ejemplo parece complicado, pero ya es uno de los más complicados. Otros, como TransformMessage para notificar sobre el cambio de posición y orientación, son más fáciles de procesar. Un TransformMassage es interesante para muchos servidores de funciones. VisualisationServer necesita actualizar la representación visual de GameObject en la pantalla. SoundServer para actualizar la posición del sonido en 3D, etc.
La ventaja de usar mensajes en lugar de invocar métodos debe ser clara. Hay un menor acoplamiento entre los componentes. Al invocar un método, la persona que llama necesita conocer al destinatario. Pero al usar mensajes esto está completamente desacoplado. Si no hay un receptor, entonces no importa. Además, la forma en que el receptor procesa el mensaje, si no es una preocupación de la persona que llama. Tal vez los delegados son una buena opción aquí, pero Java echa de menos una implementación limpia para estos y, en el caso del juego de red, necesita utilizar algún tipo de RPC, que tiene una latencia bastante alta. Y la baja latencia es crucial para los juegos interactivos.
Persistencia y clasificación
Esto nos lleva a cómo pasar mensajes a través de la red. Al encapsular la interacción de GameObject / Feature con los mensajes, solo tenemos que preocuparnos de cómo pasar mensajes a través de la red. Lo ideal sería llevar los mensajes a una forma universal y ponerlos en un paquete UDP y enviarlos. El receptor desempaqueta el mensaje a una instancia de la clase adecuada y lo canaliza al receptor o lo difunde, dependiendo del mensaje. No sé si la serialización incorporada de Java está a la altura de la tarea. Pero incluso si no, hay muchas librerías que pueden hacer esto.
GameObjects y sus componentes hacen que su estado persistente esté disponible a través de propiedades (C ++ no tiene serialización incorporada). Tienen una interfaz similar a un PropertyBag en Java con el que se puede recuperar y restaurar su estado.
Referencias
- The Brain Dump : el blog de un desarrollador de juegos profesional. También son autores del motor de código abierto Nebula, un motor de juego utilizado en juegos comercialmente exitosos. La mayor parte del diseño que presenté aquí proviene de la capa de aplicación de Nebula.
- Artículo notable en el blog anterior, establece la capa de aplicación del motor. Otro ángulo de lo que traté de describir arriba.
- Una larga discusión sobre cómo diseñar la arquitectura del juego. Principalmente Ogre específico, pero lo suficientemente general como para ser útil también para otros.
- Otro argumento para los diseños basados en componentes , con referencias útiles en la parte inferior.