Eso se puede hacer de mil formas diferentes, el límite lo pone la imaginación y los conocimientos sobre Java Swing.
Antes habíamos hecho que el programa comenzase ya con los dos únicos Pokemon listos para pelear.
Si queremos elegir entre tres (o más) habrá que cambiar varias cosas, ya que ahora el programa no puede comenzar listo para el combate.
Hay que dar alguna forma de que el usuario escoja los Pokemon, y como digo, hay mil formas.
Se puede mostrar un JComboBox con los nombres de los Pokemon, o un simple JOptionPane mostrando los nombres disponibles y que el usuario teclee el nombre del Pokemon escogido.
O podemos mostrar un ventana de dialogo con las imágenes de los Pokemon disponibles y que el usuario haga click en el que quiera.
La interfaz puede comenzar como antes, con los dos "paneles Pokemon", pero mostrando de inicio unas "PokeBall" y que el usuario haga click en ellas para escoger un Pokemon para cada panel.
Al clickar una Pokeball, aparece un JDialog, una ventana similar a un JFrame, y mostraría los Pokemon disponibles. Sean tres, sean cuatro, sean los que sean..
Al clickar un Pokemon, queda seleccionado dentro de ese PanelPokemon.
Luego clickamos en la otra Pokeball para elegir el oponente, y ahora ya sí, se puede combatir.
Como digo, para todo esto se requiere hacer varios cambios en lo que ya teníamos escrito, para facilitar el añadido.
Una de las cosas que añadiremos, será un ArrayList que contenga los Pokemon que hay disponibles en el juego.
La clase
Pokemon habrá que modificarla. Como ahora habrán varios Pokemon, cada uno con su foto, conviene que cada Pokemon tenga la ruta a su foto correspondiente.
Antes solo teníamos dos Pokemon, así que la ruta a sus fotos la poníamos "a mano" directamente en los PanelesPokemon, porque no había elección posible.
Ahora sí que hay elección, así que no sabemos de antemano que foto mostrarán los PanelesPokemon y lo mejor es que al asignarle un Pokemon a estos paneles, sea este Pokemon quien le diga donde encontrar la foto.
De hecho, le vamos a proporcionar dos fotos a cada Pokemon, una mirando a la izquierda y otra mirando a la derecha. Según a que PanelPokemon se asignado, se mostrará una imagen o la otra, para que en la interfaz los Pokemon siempre queden encarados, mirándose el uno al otro.
También he quitado del constructor el boolean para decidir si es su turno de ataque o no.
Esto antes lo decidíamos en el momento de la creación del Pokemon, por eso estaba en el constructor.
Ahora esto no se decide al construir el Pokemon, se decidirá cuando sea seleccionado desde los PanelesPokemon por el usuario, así que no tiene sentido indicarlo en el constructor.
Marco en negrita los cambios:
public class Pokemon{
private String nombre;
private int vida;
private Random azar = new Random();
private String imgP1; //Ruta a la imagen cuando es player1
private String imgP2; //Ruta a la imagen cuando es player2, misma que P1, pero invertida
public String mensaje; //Mensaje que se genera tras un ataque
public boolean suTurno; //Indica si es el turno de ataque de este Pokemon
public Pokemon(String nombrePok, int vidaPok, String img1, String img2) {
nombre = nombrePok;
vida = vidaPok;
mensaje = "";
imgP1 = img1;
imgP2 = img2;
}
public String getNombre() {
return nombre;
}
public int getVida() {
return vida;
}
public void setVida(int v) {
vida = v;
}
public String getImgP1() {
return imgP1;
}
public String getImgP2() {
return imgP2;
}
public int atacar() {
mensaje = ""; //Se generará un nuevo mensaje
int ataque = azar.nextInt(11) + 10;
if (esCritico()) {
mensaje += "¡Ataque crítico! ";
ataque = (int)(ataque * 1.5);
}
mensaje += "Daño: " + ataque + " puntos";
return ataque;
}
public void perderVida(int ataque) {
vida -= ataque;
}
private boolean esCritico() {
int valor = azar.nextInt(100);
/*
* Entre 0 y 100 hay diez valores que son
* múltiplos de 10.
* Es decir, hay un 10% de posibilidades
* de obtener un valor múltiplo de 10.
*/
return valor%10 == 0;
}
@Override
public String toString() {
return "Nombre: " + nombre + "\nVida: " + vida;
}
}
Sobre la clase
PanelPokemon, es la que va a requerir más cambios. Antes al comenzar ya estaba plenamente configurada, con su Pokemon asignado, la barra de vida ajustada al nivel de vida del Pokemon, el nombre del Pokemon,...
Ahora no, ahora ha de comenzar mostrando la foto de la Pokeball y cuando se seleccione un combatiente, ajustar nombre, foto y barra de vida según el Pokemon escogido.
Obtiene un atributo nuevo, un valor int que podrá tener dos valores: 1 ó 2.
Esto es para identificar cuál es el panel que está a la izquierda (1) y cuál a la derecha (2).
Así sabremos en que panel tenemos que poner la foto en la que el personaje mira a la derecha y en cuál la que mira a la izquierda.
Los "subpaneles" que componen este panel ahora estarán referenciados como atributos de clase para poder interactuar con ellos. Antes los instanciábamos directamente sin referenciarlos, porque no se requería acceder a ningún método que pudieran tener.
Ahora estos paneles si tendrán métodos.
PanelNombre tiene un método para configurar el nombre y
PanelImagen otro para cambiar la imagen.
A esta clase interna,
PanelImagen, le vamos a implementar un MouseListener para detectar cuándo el usuario hace click en la imagen.
Cuando esto ocurre, es cuando hacemos aparecer el JDialog que permite seleccionar Pokemon.
A este JDialog hay que pasarle unos parámetros, uno de ellos va a ser precisamente una referencia al PanelPokemon desde el cuál ha sido invocado.
De este modo, desde el JDialog podremos setear un Pokemon para el PanelPokemon e invocar a los métodos necesarios para que se configure respecto al Pokemon asignado.
Esta es la clase
PanelPokemon reescrita. Atención especial al MouseListener de la subclase
PanelImagenpublic class PanelPokemon extends JPanel{
public Pokemon pokemon;
private int numPanel;
private JButton btAtacar;
private JProgressBar barraVida;
private PanelNombre pnNombre;
private PanelImagen pnImagen;
private PanelVida pnVida;
//Autoreferencia para poder transmitirsela al JDialog que selecciona Pokemon
private PanelPokemon estePanel;
public PanelPokemon(String rutaImagen, int numero) {
numPanel = numero;
pnNombre = new PanelNombre();
pnImagen = new PanelImagen(rutaImagen);
pnVida = new PanelVida();
setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
add(pnNombre);
add(pnImagen);
add(pnVida);
add(new PanelBoton());
estePanel = this;
}
public void reiniciar() {
if (numPanel == 1)
pnImagen.setImagen("img/pokeball_215.png");
else
pnImagen.setImagen("img/pokeball_215_p2.png");
btAtacar.setEnabled(false);
pnNombre.setNombre("Escoge Pokemon");
barraVida.setValue(0);
}
/*
* Este método lo invocará el JDialog que permite
* seleccionar Pokemon, para setearle un Pokemon
* a esta clase PanelPokemon.
* Para poder invocar este método, el JDialog necesita
* recibir una referencia a esta clase para que haya
* visibilidad entre ellas.
*/
public void setPokemon(Pokemon pok) {
pokemon = pok;
pnNombre.setNombre(pokemon.getNombre());
if (numPanel == 1) {
pnImagen.setImagen(pokemon.getImgP1());
pokemon.suTurno = true;
}
else {
pnImagen.setImagen(pokemon.getImgP2());
pokemon.suTurno = false;
}
barraVida.setMaximum(pokemon.getVida());
barraVida.setValue(pokemon.getVida());
btAtacar.setEnabled(pokemon.suTurno);
}
private class PanelNombre extends JPanel {
private JLabel nombre;
public PanelNombre() {
nombre = new JLabel("Escoge Pokemon");
nombre.setFont(new Font("Consolas", Font.BOLD, 34));
nombre.setForeground(Color.WHITE);
add(nombre);
setBorder(BorderFactory.createBevelBorder(BevelBorder.RAISED, Color.WHITE, Color.BLACK));
setBackground(new Color(23, 151, 238));
}
public void setNombre(String nombrePok) {
nombre.setText(nombrePok);
}
}
private class PanelImagen extends JPanel implements MouseListener{
private JLabel icono;
public PanelImagen(String rutaImagen) {
ImageIcon imagen = new ImageIcon(PanelPokemon.class.getClassLoader().getResource(rutaImagen));
icono = new JLabel(imagen);
icono.setBorder(BorderFactory.createLoweredSoftBevelBorder());
add(icono);
addMouseListener(this);
}
public void setImagen(String ruta) {
ImageIcon imagen = new ImageIcon(PanelPokemon.class.getClassLoader().getResource(ruta));
icono.setIcon(imagen);
}
@Override
public void mouseClicked(MouseEvent e) {
new Selector(null, true, estePanel); //JDialog recibe referencia a esta misma clase
}
@Override
public void mousePressed(MouseEvent e) { }
@Override
public void mouseReleased(MouseEvent e) { }
@Override
public void mouseEntered(MouseEvent e) { }
@Override
public void mouseExited(MouseEvent e) { }
}
private class PanelVida extends JPanel {
public PanelVida() {
barraVida = new JProgressBar();
barraVida.setMaximum(0);
barraVida.setValue(0);
barraVida.setPreferredSize(new Dimension(200, 30));
barraVida.setForeground(Color.BLUE);
add(barraVida);
barraVida.setBorder(BorderFactory.createRaisedSoftBevelBorder());
TitledBorder bordeTitulado = new TitledBorder("Nivel de Vida");
bordeTitulado.setTitleFont(new Font("Consolas", Font.ITALIC, 24));
setBorder(bordeTitulado);
}
}
private class PanelBoton extends JPanel {
public PanelBoton() {
btAtacar = new JButton("¡ATACAR!");
btAtacar.setEnabled(false);
btAtacar.setFont(new Font("Consolas", Font.BOLD, 30));
add(btAtacar);
setBorder(BorderFactory.createEmptyBorder(30, 20, 30, 30));
}
}
public void actualizarPanel() {
barraVida.setValue(pokemon.getVida());
btAtacar.setEnabled(pokemon.suTurno);
}
public void agregarAccion(ActionListener accion) {
btAtacar.addActionListener(accion);
}
}
Como hemos dicho, al hacer click en
PanelImagen, se instancia la clase
Selector que es un JDialog que aparece en pantalla con 8 Pokemon seleccionables.
Esta clase será quien tenga un ArrayList con 8 objetos Pokemon ya preconfigurados y listos para ser seleccionados.
En pantalla se muestran en una grilla de 2 filas y 4 columnas.
Cada imagen de estos Pokemon seleccionables, se construye mediante la subclase
PanelPersonaje la cuál también implementa un MouseListener.
En esta ocasión, el MouseListener no solo responderá ante el click del ratón, también responde al momento en que la flechita del ratón pasa por encima.
De este modo, al pasar el ratón por un personaje, el fondo cambia de color para que quede resaltado respecto a los demás.
Cuando el ratón abandona su espacio, recupera el color habitual.
Cuando se hace click, se setea el Pokemon seleccionado mediante la referencia al
PanelPokemon que lo ha invocado y se cierra el
Selector de Pokemon.
public final class Selector extends JDialog{
private ArrayList<Pokemon> pokedex;
private PanelPokemon panelPokemon; //Referencia al panel de jugador que invoca este selector
public Selector(Frame parent, boolean modal, PanelPokemon pnPok) {
super(parent, modal);
panelPokemon = pnPok;
construirPokedex();
setLayout(new BorderLayout());
PanelMensajes pnMensaje = new PanelMensajes();
pnMensaje.setMensaje("Selecciona un personaje Pokemon");
add(pnMensaje, BorderLayout.NORTH);
JPanel roaster = new JPanel();
roaster.setLayout(new GridLayout(2, 4)); //Grilla para los 8 pokemons
for (Pokemon pok: pokedex)
roaster.add(new PanelPersonaje(pok));
add(roaster, BorderLayout.CENTER);
setTitle("Seleccionar Pokemon");
setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
pack();
setLocationRelativeTo(null);
setResizable(false);
setVisible(true);
}
private void construirPokedex() {
pokedex = new ArrayList<Pokemon>();
pokedex.add(new Pokemon("Bulbasaur", 200, "img/bulbasaur_p1.png", "img/bulbasaur_p2.png"));
pokedex.add(new Pokemon("Caterpie", 200, "img/caterpie_p1.png", "img/caterpie_p2.png"));
pokedex.add(new Pokemon("Charmander", 200, "img/charmander_p1.png", "img/charmander_p2.png"));
pokedex.add(new Pokemon("Pidgey", 200, "img/pidgey_p1.png", "img/pidgey_p2.png"));
pokedex.add(new Pokemon("Pikachu", 200, "img/pikachu_p1.png", "img/pikachu_p2.png"));
pokedex.add(new Pokemon("Rattata", 200, "img/rattata_p1.png", "img/rattata_p2.png"));
pokedex.add(new Pokemon("Squirtle", 200, "img/squirtle_p1.png", "img/squirtle_p2.png"));
pokedex.add(new Pokemon("Weedle", 200, "img/weedle_p1.png", "img/weedle_p2.png"));
}
private void cerrarDialogo() {
dispose();
}
private class PanelPersonaje extends JPanel implements MouseListener{
public Pokemon personaje;
public PanelPersonaje(Pokemon pok) {
personaje = pok;
ImageIcon imagen = new ImageIcon(
Selector.class.getClassLoader().getResource(personaje.getImgP1()));
add(new JLabel(imagen));
setToolTipText(personaje.getNombre());
setBorder(new MatteBorder(15, 15, 15, 15, Color.DARK_GRAY));
setBackground(Color.WHITE);
addMouseListener(this);
}
@Override
public void mouseClicked(MouseEvent e) {
panelPokemon.setPokemon(personaje);
cerrarDialogo();
}
@Override
public void mousePressed(MouseEvent e) { }
@Override
public void mouseReleased(MouseEvent e) { }
@Override
public void mouseEntered(MouseEvent e) {
setBackground(Color.YELLOW);
}
@Override
public void mouseExited(MouseEvent e) {
setBackground(Color.WHITE);
}
}
}
Cuando ya se han seleccionado los Pokemon, se puede comenzar la batalla.
La lógica de la batalla se lleva a cabo en la clase principal,
BatallaSwing.
Esta clase requiere pocos cambios, total, en realidad tiene muy poco código.
Antes teníamos a dos Pokemon como atributos, ahora ya no. Interactuaremos directamente con los objetos Pokemon que contiene cada
PanelPokemon.
public class BatallaSwing extends JFrame{
private PanelMensajes pnMensajes;
private PanelPokemon pnPok1;
private PanelPokemon pnPok2;
public BatallaSwing() {
pnMensajes = new PanelMensajes();
pnPok1 = new PanelPokemon("img/pokeball_215.png", 1);
pnPok1.agregarAccion(new AccionAtacar());
pnPok2 = new PanelPokemon("img/pokeball_215_p2.png", 2);
pnPok2.agregarAccion(new AccionAtacar());
setLayout(new BorderLayout());
add(pnMensajes, BorderLayout.NORTH);
JPanel pnCentro = new JPanel();
pnCentro.add(pnPok1);
pnCentro.add(new PanelVS()); //PanelVS no requiere referencia, no se interactúa con ella.
pnCentro.add(pnPok2);
add(pnCentro, BorderLayout.CENTER);
setTitle("Batalla Pokemon");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
pack();
setLocationRelativeTo(null);
setVisible(true);
}
private class AccionAtacar implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
if (pnPok1.pokemon.suTurno) {
pnPok2.pokemon.perderVida(pnPok1.pokemon.atacar());
pnMensajes.setMensaje(pnPok1.pokemon.mensaje);
pnPok1.pokemon.suTurno = false;
pnPok2.pokemon.suTurno = true;
}
else {
pnPok1.pokemon.perderVida(pnPok2.pokemon.atacar());
pnMensajes.setMensaje(pnPok2.pokemon.mensaje);
pnPok1.pokemon.suTurno = true;
pnPok2.pokemon.suTurno = false;
}
pnPok1.actualizarPanel();
pnPok2.actualizarPanel();
if (pnPok1.pokemon.getVida() <= 0 || pnPok2.pokemon.getVida() <= 0) {
String mensajeFinal = "Juego Terminado\n Ha ganado: ";
mensajeFinal += pnPok1.pokemon.getVida()>0?pnPok1.pokemon.getNombre():pnPok2.pokemon.getNombre();
JOptionPane.showMessageDialog(null, mensajeFinal, "Fin del Juego",
JOptionPane.INFORMATION_MESSAGE);
pnMensajes.setMensaje("Clicka las Pokeball para escoger Pokemon");
pnPok1.reiniciar();
pnPok2.reiniciar();
}
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
new BatallaSwing();
}
});
}
}
Y eso es todo.
Tiene algunos fallos, por ejemplo al seleccionar un Pokemon en el lazo izquierdo, ya podemos pulsar el botón ATACAR sin haber seleccionad un rival en el otro lado.
Visualmente parece que no pasa nada pero en realidad ocurre una NullPointerException cada vez que se pulsa el botón sin tener rival escogido.
Otro fallo es que durante el combate, en cualquier momento podemos volver a escoger otro Pokemon, o el mismo, y el nuevo Pokemon comienza con la vida entera mientras el rival sigue con la barra de vida reducida por los daños.
Pero bueno, son pequeños detalles que se pueden ir puliendo.
He intentado explicar los cambios hechos lo mejor posible, pero preguntad cualquier cosa que no se entienda.
Proporciono el código fuente de dos formas:
- Una, solo el código, adjuntándolo en este mismo mensaje del foro
- Otra, código y las imágenes que he usado, en este
enlace Google DriveSe pueden usar las imágenes que uno quiera, basta con incluirlas dentro de un package en nuestro proyecto. Yo he seguido esta estructura: