Mundo Maker
¡Bienvenid@ a Mundo Maker!

¿Quieres aprender todo sobre el RPG Maker?



Regístrate y forma parte de Mundo Maker.

[XP] Advanced Ring Menu v1.0

Ver el tema anterior Ver el tema siguiente Ir abajo

[XP] Advanced Ring Menu v1.0

Mensaje por Wecoc el 2015-01-31, 00:51

Hice un script Ring Menu avanzado, para gente poco satisfecha con lo que permite el actual.
Viene a ser lo mismo pero más globalizado y con muchas propiedades más.



Está todo detallado en las instrucciones del script, pero a grosso modo, permite controlar los tiempos de aparición y movimiento, el sonido y la trayectoria de aparición, la forma que dibujan los iconos, y el zoom del icono seleccionado, la opacidad de los iconos no seleccionados. Además a parte del típico con iconos fuera y el texto del comando dentro, puedes poner también los textos fuera de modo que también giren, incluso sin iconos.

Lo más interesante a mi parecer es que hasta ahora el Ring Menu era para el menú y punto pelota (excepto algunos casos que ví en Scene_Title...)

Éste lo puedes insertar donde quieras como si fuera una Window_Command. Obviamente necesitarás ciertos conocimientos de script pero bueno.

Su llamada es:

@window = Window_RingMenu.new([commands, icons], x, y, ratio)
(ratio es opcional y por defecto vale 64)

Aquí tenéis el script:

Código:
#==============================================================================
# [XP] Advanced Ring Menu v1.0
#------------------------------------------------------------------------------
# Autor: Wecoc
# Adaptado de la modificación de Reno-s--Joker & Dubealex del script original.
# Puedes: Modificar el script, y aportarlo a otras comunidades.
# Créditos requeridos.
#------------------------------------------------------------------------------
# Éste script permite un mejor control del Ring Menu, aunque se requiere un
# mínimo nivel de scripting para ello.
#
# <<<<<< PROPIEDADES >>>>>>
#
# Puedes controlar las propiedades definidas en Game_Temp desde el juego.
#
# - $game_temp.ring_moving_frames = n
#     Número de frames necesarios para hacer el movimiento de rotación
#
# - $game_temp.ring_startup_frames = n
#     Número de frames necesarios para hacer el movimiento de inicio
#
# - $game_temp.ring_startup_se = (name)
#     Sonido de inicio del ring
#
# - $game_temp.ring_startup_mode = n
#   Tipo de inicio del Ring. Puedes definir los que quieras en refresh_start
#   Los que puse por defecto son:
#     0 - Ningún efecto
#     1 - Desde el centro, con rotación (por defecto)
#     2 - Desde el centro, sin rotación
#     3 - Desde su ratio final, con rotación y fade in (opacidad)
#
# - $game_temp.ring_mode = n
#   Tipo de Ring según su fórmula para calcular x e y (normalmente cos y sin)
#   Puedes definir los que quieras en get_ring_x // get_ring_y
#   Los que puse por defecto son:
#     0 - Circunferencia (por defecto)
#     1 - Elipse
#
# - $game_temp.ring_by_coords = true/false
#     Si vale true, puedes controlar cada posición del índice manualmente.
#     Por ejemplo, tras llamar un Ring con 3 comandos puedes usar:
#       @command_window.coords = [[100, 100], [160, 160], [160, 40]]
#    
# - $game_temp.ring_edge_hide_fix = true/false
#     Si vale true, el Ring no quedará nunca fuera de los límites de la ventana
#
# - $game_temp.ring_center_text = true/false
#     Si vale true, el texto del comando actual sale en medio del Ring
#     Si vale false en cambio el texto queda al lado de cada icono
#
# - $game_temp.ring_index_zoom = float
#     Define el zoom del icono seleccionado, por defecto 1.0
#
# - $game_temp.ring_opacity = n
#     Define la opacidad de los iconos no seleccionados, por defecto 255
#
# - $game_temp.ring_auto_radius = true/false
#     Si vale true calcula directamente el radio del ring.
#     Lo hace usando el ancho máximo de los textos de los comandos
#
# <<<<<< LLAMADAS >>>>>>
#
# - Para llamar una nueva ventana debes usar:
#   Window_RingMenu.new([(comandos), (iconos)], x, y[, ratio])
#
#   Ejemplo:
#   commands = ["Items", "Equip", "Status", "Quit"]
#   icons = ["001-Weapon01", "002-Weapon02", "003-Weapon03", "004-Weapon04"]
#   @command_window = Window_RingMenu.new([commands, icons], 100, 240)
#
# - Para desactivar algunos comandos debes usar:
#   window.disabled = [(índices)]
#
#   Ejemplo:
#   @command_window.disabled = [2, 3]
#
# <<<<<< IDEAS / COSAS POR HACER... >>>>>>
#
# - Posibilidad de definir una coordenada extra de inicio y fín (en coords)
#   de modo que el primer y el último vayan allí controlando su opacidad
#   (Es decir, modo "cíclico" contra modo "no cíclico")
#
# - Ángulo modificable para el comando seleccionado
#
# - Ejemplos usables: 1. Scene_Menu y 2. Choices de Scene_Map
#
# - Opción de saltarse los comandos disabled directamente al girar
#
# - Permitir iconos de tamaño diferente a 24x24
#
# - Efectos al terminar (dispose) similares a los de startup
#
#  ¿Tienes otras ideas? ¡Compártelas!
#==============================================================================

#===============================================================================
# * Game_Temp
#===============================================================================

class Game_Temp
  
  attr_accessor :ring_moving_frames   # Frames necesarios para rotar el índice
  attr_accessor :ring_startup_frames  # Frames necesarios al iniciar Ring
  attr_accessor :ring_startup_se      # Sonido al iniciar Ring
  attr_accessor :ring_startup_mode    # Tipo de inicio
  attr_accessor :ring_mode            # Tipo de Ring
  attr_accessor :ring_by_coords       # Control por coordenadas
  attr_accessor :ring_edge_hide_fix   # Evitar que sobresalga de la pantalla
  attr_accessor :ring_center_text     # Mostrar texto en medio
  attr_accessor :ring_index_zoom      # Zoom del índice seleccionado
  attr_accessor :ring_opacity         # Opacidad de los índices no seleccionados
  attr_accessor :ring_auto_radius     # Calcular radio automáticamente
  
  alias ring_adv_ini initialize unless $@
  def initialize
    ring_adv_ini
    default_ring_features
  end
  
  def default_ring_features
    @ring_moving_frames = 5
    @ring_startup_frames = 15
    @ring_startup_se = "056-Right02"
    @ring_startup_mode = 1
    @ring_mode = 0
    @ring_by_coords = false
    @ring_edge_hide_fix = true
    @ring_center_text = true
    @ring_index_zoom = 1.0
    @ring_opacity = 255
    @ring_auto_radius = false
  end
end

#===============================================================================
# * Sprite_RingCommand
#===============================================================================

class Sprite_RingCommand < Sprite
  def initialize(viewport, text, icon)
    super(viewport)
    self.bitmap = Bitmap.new(1, 1)
    # Texto del comando (puede valer nil)
    @text = text
    # Icono del comando (puede valer nil)
    @icon = icon
    # Calcular tamaño del sprite
    ## Puedes probar otras combinaciones, como texto e icono en columna
    width = 0
    height = 0
    if icon != nil
      width += 24
      height += 24
    end
    ## Aquí puedes definir propiedades a la font
    if text != nil
      width += self.bitmap.text_size(text).width
      height = [height, self.bitmap.text_size(text).height].max
    end
    if icon != nil && text != nil
      width += 4
      height += 4
    end
    self.bitmap = Bitmap.new(width, height)
    self.ox = width / 2
    self.oy = height / 2
    # Refrescar comando
    @disabled = false
    refresh
  end
  
  # Color de activado y desactivado, corresponden a los de Window_Base
  
  def normal_color
    return Color.new(255, 255, 255, 255)
  end

  def disabled_color
    return Color.new(255, 255, 255, 128)
  end
  
  # Activar o desactivar el comando
  
  def enable
    @disabled = false
    refresh
  end
  
  def disable
    @disabled = true
    refresh
  end
  
  # Dibujar el comando (icono y texto)
  
  def refresh
    self.bitmap.clear
    self.bitmap.font.color = @disabled ? disabled_color : normal_color
    opacity = @disabled ? 128 : 255
    x = 0
    # Dibujar icono
    if @icon != nil
      rect = Rect.new(0, 0, 24, 24)
      icon = RPG::Cache.icon(@icon)
      self.bitmap.blt(0, 0, icon, rect, opacity)
      x += 24 + 4
    end
    # Dibujar texto
    if @text != nil
      self.bitmap.draw_text(x, 0, self.bitmap.width, 24, @text)
    end
  end
end

#===============================================================================
# * Window_RingMenu
#===============================================================================

class Window_RingMenu < Window_Base

  MODE_START = 1
  MODE_WAIT  = 2
  MODE_MOVEL = 3
  MODE_MOVER = 4
  
  attr_accessor :index
  attr_accessor :coords
  attr_reader :disabled
  
  def initialize(command_array, center_x, center_y, radius=64)
    super(0, 0, 640, 480)
    self.contents = Bitmap.new(width-32, height-32)
    self.opacity = 0
    self.back_opacity = 0
    # Definir comandos e iconos a partir de command_array
    if command_array.size == 2
      @commands = command_array[0]
      @icons = command_array[1]
    else
      @commands = command_array
      @icons = []
    end
    @item_max = @commands.size
    @index = 0
    # Definir radio del Ring
    if $game_temp.ring_auto_radius == true
      width = 0
      for i in 0...@commands.size
        width = [width, self.contents.text_size(@commands[i]).width].max
      end
      @radius = width
    else
      @radius = radius
    end
    # No hay iconos desactivados por defecto
    @disabled = []
    # Definir posición del Ring en la pantalla
    if $game_temp.ring_edge_hide_fix == true
      cx = center_x - 16
      cy = center_y - 16
      max_x = (cx + radius) + 64
      min_x = (cx - radius)
      max_y = (cy + radius) + 48
      min_y = (cy - radius)
      cx -= (max_x - 640) if max_x > 640
      cy -= (max_y - 480) if max_y > 480
      cx -= min_x if min_x < 0
      cy -= min_y if min_y < 0
      @cx = cx
      @cy = cy
    else
      @cx = center_x - 16
      @cy = center_y - 16
    end
    # Definir las coordenadas en el caso de que vaya por coordenadas
    if $game_temp.ring_by_coords == true
      start_coords
    end
    create_sprites
    setup_move_start
    refresh
  end

  def dispose
    super
    @sprites.each do |sprite|
      sprite.bitmap.dispose
      sprite.dispose
    end
  end
    
  # Definir los comandos desactivados
  
  def disabled=(value)
    @disabled = value
    @disabled = [] if @disabled == nil
    for i in 0...@commands.size
      if !@disabled.include?(i)
        @sprites[i].enable
      else
        @sprites[i].disable
      end
    end
  end
  
  # Crear los sprites que giran (iconos)
  
  def create_sprites
    @sprites = []
    for i in 0...@commands.size
      if $game_temp.ring_center_text
        sprite = Sprite_RingCommand.new(@viewport, nil, @icons[i])
      else
        sprite = Sprite_RingCommand.new(@viewport, @commands[i], @icons[i])
      end
      @sprites.push(sprite)
    end
  end
  
  # Establecer las coordenadas del Ring (si va por coordenadas)
  
  def start_coords
    @coords = []
    for i in 0...@item_max
      d = i * 2.0 * Math::PI / @item_max
      @coords[i] = [get_ring_x(d, @radius, 1), get_ring_y(d, @radius, 1)]
    end
  end
  
  # Update
  
  def update
    super
    @sprites.each do |sprite|
      sprite.update
      sprite.opacity = [self.contents_opacity, $game_temp.ring_opacity].min
      sprite.zoom_x = 1.0
      sprite.zoom_y = 1.0
    end
    @sprites[@index].zoom_x = $game_temp.ring_index_zoom
    @sprites[@index].zoom_y = $game_temp.ring_index_zoom
    @sprites[@index].opacity = self.contents_opacity
    refresh
  end
  
  # Refresh restrictivo
  
  def refresh
    self.contents.clear
    case @mode
    when MODE_START # Iniciar Ring
      refresh_start
    when MODE_WAIT # Dibujar Ring parado
      refresh_wait
    when MODE_MOVER # Dibujar Ring ->
      refresh_move(1)
    when MODE_MOVEL # Dibujar Ring <-
      refresh_move(0)
    end
    # Escribir el texto del comando activo en medio del Ring
    if $game_temp.ring_center_text
      rect = Rect.new(@cx - 272, @cy + 24, self.contents.width - 32, 32)
      if !@disabled.include?(@index)
        self.contents.font.color = normal_color
      else
        self.contents.font.color = disabled_color
      end
      self.contents.draw_text(rect, @commands[@index], 1)
    end
  end

  # Refresh al Iniciar Ring
  
  def refresh_start
    frames = $game_temp.ring_startup_frames
    if $game_temp.ring_by_coords == true
      refresh_start_coords
      return
    end
    case $game_temp.ring_startup_mode
    when 0 # Ningún efecto
      d = 2.0 * Math::PI / @item_max
      for i in 0...@item_max
        j = i - @index
        x = get_ring_x(d, @radius, j)
        y = get_ring_y(d, @radius, j)
        draw_menu_item(x, y, i)
      end
      @steps = 0
      @mode = MODE_WAIT
    when 1 # Desde dentro (con rotación)
      d1 = 2.0 * Math::PI / @item_max
      d2 = 1.0 * Math::PI / frames
      r = @radius - 1.0 * @radius * @steps / frames
      for i in 0...@item_max
        j = i - @index
        d = d1 * j + d2 * @steps
        x = get_ring_x(d, r, 1)
        y = get_ring_y(d, r, 1)
        draw_menu_item(x, y, i)
      end
      @steps -= 1
      if @steps < 1
        @mode = MODE_WAIT
      end
    when 2 # Desde dentro (sin rotación)
      d1 = 2.0 * Math::PI / @item_max
      d2 = 1.0 * Math::PI * frames
      r = @radius - 1.0 * @radius * @steps / frames
      for i in 0...@item_max
        j = i - @index
        d = d1 * j + d2 * @steps
        x = get_ring_x(d, r, 1)
        y = get_ring_y(d, r, 1)
        draw_menu_item(x, y, i)
      end
      @steps -= 1
      if @steps < 1
        @mode = MODE_WAIT
      end
    when 3 # Rotación + Opacidad
      d1 = 2.0 * Math::PI / @item_max
      d2 = 1.0 * Math::PI / frames
      r = @radius + 1.0 / @radius / @steps * frames
      self.contents_opacity = 255 - (255 * @steps / frames)
      for i in 0...@item_max
        j = i - @index
        d = d1 * j + d2 * @steps
        x = get_ring_x(d, r, 1)
        y = get_ring_y(d, r, 1)
        draw_menu_item(x, y, i)
      end
      @steps -= 1
      if @steps < 1
        @mode = MODE_WAIT
      end
    # when 4
      # Puedes poner más efectos aquí
    end
  end

  # Refresh al Iniciar Ring (Ring por coordenadas)

  def refresh_start_coords
    frames = $game_temp.ring_startup_frames
    case $game_temp.ring_startup_mode
    when 0 # Ningún efecto
      for i in 0...@item_max
        x = @coords[i][0]
        y = @coords[i][1]
        draw_menu_item(x, y, i)
      end
      @steps = 0
      @mode = MODE_WAIT
    when 1 # Desde dentro (con rotación)
      for i in 0...@item_max
        j = i - @index
        sx = @cx
        sy = @cy
        max = @coords.size
        ex1 = @coords[(j+1) % max][0]
        ey1 = @coords[(j+1) % max][1]
        ex2 = @coords[j][0]
        ey2 = @coords[j][1]
        dex = (ex2 - ex1) * @steps / frames
        dey = (ey2 - ey1) * @steps / frames
        ex = ex2 - dex
        ey = ey2 - dey
        dx = (ex - sx) * @steps / frames
        dy = (ey - sy) * @steps / frames
        x = ex - dx
        y = ey - dy
        draw_menu_item(x, y, i)
      end
      @steps -= 1
      if @steps < 1
        @mode = MODE_WAIT
      end
    when 2 # Desde dentro (sin rotación)
      for i in 0...@item_max
        j = i - @index
        sx = @cx
        sy = @cy
        ex = @coords[j][0]
        ey = @coords[j][1]
        dx = (ex - sx) * @steps / frames
        dy = (ey - sy) * @steps / frames
        x = ex - dx
        y = ey - dy
        draw_menu_item(x, y, i)
      end
      @steps -= 1
      if @steps < 1
        @mode = MODE_WAIT
      end
    when 3 # Rotación + Opacidad
      if @step_fix
        self.contents_opacity = 255 - (128 * @steps / frames)
      else
        self.contents_opacity = 128 - (128 * @steps / frames)
      end
      for i in 0...@item_max
        j = i - @index
        max = @coords.size
        if @step_fix
          sx = @coords[(j+1) % max][0]
          sy = @coords[(j+1) % max][1]
          ex = @coords[j][0]
          ey = @coords[j][1]
        else
          sx = @coords[(j+2) % max][0]
          sy = @coords[(j+2) % max][1]
          ex = @coords[(j+1) % max][0]
          ey = @coords[(j+1) % max][1]
        end
        dx = (ex - sx) * @steps / frames
        dy = (ey - sy) * @steps / frames
        x = ex - dx
        y = ey - dy
        draw_menu_item(x, y, i)
      end
      @steps -= 2
      if @steps < 1
        if @step_fix == true
          @step_fix = false
          @mode = MODE_WAIT
        else
          @step_fix = true
          @steps = frames
        end
      end
    # when 4
      # Puedes poner más efectos aquí
    end
  end
  
  # Fórmulas para obtener la posición de cada índice
  
  def get_ring_x(d, r, j)
    case $game_temp.ring_mode
    when 0 # Circunferencia (default)
      @cx + ( r * Math.sin(d * j) ).to_i
    when 1 # Elipse
      @cx + ( r * Math.sin(d * j) ).to_i
    #when 2
      # Puedes poner más
    end
  end
  
  def get_ring_y(d, r, j)
    case $game_temp.ring_mode
    when 0 # Circunferencia (default)
      @cy - ( r * Math.cos(d * j) ).to_i
    when 1 # Elipse
      @cy - (( r * Math.cos(d * j) ).to_i) * 0.75 + @radius / 4
    #when 2
      # Puedes poner más
    end
  end
  
  # Refresh cuando el Ring está parado
  
  def refresh_wait
    if $game_temp.ring_by_coords == true
      refresh_wait_coords
      return
    end
    d = 2.0 * Math::PI / @item_max
    for i in 0...@item_max
      j = i - @index
      x = get_ring_x(d, @radius, j)
      y = get_ring_y(d, @radius, j)
      draw_menu_item(x, y, i)
    end
  end

  # Refresh cuando el Ring está parado (Ring por coordenadas)
  
  def refresh_wait_coords
    for i in 0...@item_max
      j = i - @index
      x = @coords[j][0]
      y = @coords[j][1]
      draw_menu_item(x, y, i)
    end
  end
  
  # Refresh cuando el Ring está moviéndose
  
  def refresh_move(mode)
    if $game_temp.ring_by_coords == true
      refresh_move_coords(mode)
      return
    end
    frames = $game_temp.ring_moving_frames
    d1 = 2.0 * Math::PI / @item_max
    d2 = d1 / frames
    d2 *= -1 if mode != 0
    for i in 0...@item_max
      j = i - @index
      d = d1 * j + d2 * @steps
      x = get_ring_x(d, @radius, 1)
      y = get_ring_y(d, @radius, 1)
      draw_menu_item(x, y, i)
    end
    @steps -= 1
    if @steps < 1
      @mode = MODE_WAIT
    end
  end
  
  # Refresh cuando el Ring está moviéndose (Ring por coordenadas)
  
  def refresh_move_coords(mode)
    frames = $game_temp.ring_moving_frames
    for i in 0...@item_max
      j = i - @index
      sx = @coords[j][0]
      sy = @coords[j][1]
      n = case mode
        when 0 then 1
        when 1 then -1
      end
      max = @coords.size
      ex = @coords[(j+n) % max][0]
      ey = @coords[(j+n) % max][1]
      dx = (sx - ex) * @steps / frames
      dy = (sy - ey) * @steps / frames
      x = sx - dx
      y = sy - dy
      draw_menu_item(x, y, i)
    end
    @steps -= 1
    if @steps < 1
      @mode = MODE_WAIT
    end
  end
  
  # Refrescar las coordenadas de cada sprite
  
  def draw_menu_item(x, y, i)
    @sprites[i].x = x + 32
    @sprites[i].y = y + 48
  end

  # Definir inicio de la ventana
  
  def setup_move_start
    @mode = MODE_START
    @steps = $game_temp.ring_startup_frames
    # Reproducir sonido de inicio
    if $game_temp.ring_startup_se != nil and $game_temp.ring_startup_se != ""
      Audio.se_play("Audio/SE/" + $game_temp.ring_startup_se, 80, 100)
    end
  end

  # Definir inicio de movimiento
  
  def setup_move_move(mode)
    if mode == MODE_MOVER
      @index -= 1
      @index = @commands.size - 1 if @index < 0
    elsif mode == MODE_MOVEL
      @index += 1
      @index = 0 if @index >= @commands.size
    else
      return
    end
    @mode = mode
    @steps = $game_temp.ring_moving_frames
  end

  # Comprobar si el Ring se está moviendo
  
  def animation?
    return @mode != MODE_WAIT
  end
 
  # Activar y desactivar índices
 
  def disable_item(index)
    @disabled.push(index)
    @disabled.uniq!
    return @disabled
  end
 
  def enable_item(index)
    @disabled.delete(index)
  end
end


Además, hice un script de ejemplo que podéis ver aquí:

Código:
class Scene_Test
  def main
    commands = ["Items", "Equip", "Status", "Save", "Quit"]
    icons = ["001-Weapon01", "002-Weapon02", "003-Weapon03", "004-Weapon04", "005-Weapon05"]
    @command_window = Window_RingMenu.new([commands, icons], 100, 240)
    Graphics.transition
    loop do
      Graphics.update
      Input.update
      update
      if $scene != self
        break
      end
    end
    Graphics.freeze
    @command_window.dispose
  end
  
  def update
    @command_window.update
    if @command_window.active and !@command_window.animation?
      if Input.press?(Input::UP) or Input.press?(Input::LEFT)
        $game_system.se_play($data_system.cursor_se)
        @command_window.setup_move_move(Window_RingMenu::MODE_MOVER)
        return
      end
      if Input.press?(Input::DOWN) or Input.press?(Input::RIGHT)
        $game_system.se_play($data_system.cursor_se)
        @command_window.setup_move_move(Window_RingMenu::MODE_MOVEL)
        return
      end
    end
  end
end


Para llamar el script de ejemplo desde un evento usad $scene = Scene_Test.new

Mis objetivos son ampliar el sistema con lo que puse en el último apartado de las instrucciones (aunque son detalles, con lo que hay ahora ya podéis hacer mucho xDD) y hacer dos sistemas, uno de menú similar a los que ya hay, y uno mucho más complejo para las Choices de los mensajes.

Me gustaría que me diérais opiniones, ideas... Bueno, ya sabéis que todo es bienvenido xD

¿Por qué hice ésto?:
En Chaos Project pidieron un Choice System con Ring Menu. Se lo solucionaron hace ya 10 días, pero la solución dada, al igual que los menús hechos con Ring Menu, es muy puntual para su caso así que decidí hacer algo mucho más global.

Además yo mismo usé un Menú de éste tipo hace tiempo, en The Ukawa Quest, y encontré que era una alternativa interesante visualmente al menú de siempre pero con la gran pega de que todos los Ring Menu de RPG maker se ven exactamente igual, por lo que ya no era nada original. Con éste, si se hace bien (especialmente cuando esté listo xD), puedes llegar a hacer cosas muy distintas a un "Ring Menu".
avatar
Wecoc
Administrador
Administrador



Créditos 12310

Gracias : 654

Volver arriba Ir abajo

Ver el tema anterior Ver el tema siguiente Volver arriba


Permisos de este foro:
No puedes responder a temas en este foro.