- Joined
- Apr 9, 2015
- Messages
- 1,797
- Reaction score
- 863
- First Language
- German
- Primarily Uses
Heyhey,
i've been working on this script for the past few day and i think it's in a good enough state to post it here.
The script creates in-game maps from text files, during runtime, and is aimed at puzzle games where levels are simple enough to be represented by a few different chars. It allows for level creation on the fly and without rebuilding the game. I commented everything in the script, but if there are any questions i'd be happy to answer them.
The script is in a working state and i'd like to hear some suggestions about further script features.
For a demo, i included a simple Sokoban game. You can grab any level from here (http://sneezingtiger.com/sokoban/levels.html, click "View as text") and drop it into the "Levels" folder of the game. Highscores are saved by creating a CRC32 hash of the game data and using that as a key (not part of the script).
Demo:
https://www.dropbox.com/s/b46kn0pfxcanlz5/Sokoban.exe?dl=0
Demo Credits:
(Music) DeceasedSuperiorTechnician (http://www.nosoapradio.us/)
(Graphics) Kenney (http://www.kenney.nl/)
(Scripts) Glitchfinder, Source
Script:
i've been working on this script for the past few day and i think it's in a good enough state to post it here.
The script creates in-game maps from text files, during runtime, and is aimed at puzzle games where levels are simple enough to be represented by a few different chars. It allows for level creation on the fly and without rebuilding the game. I commented everything in the script, but if there are any questions i'd be happy to answer them.
The script is in a working state and i'd like to hear some suggestions about further script features.
For a demo, i included a simple Sokoban game. You can grab any level from here (http://sneezingtiger.com/sokoban/levels.html, click "View as text") and drop it into the "Levels" folder of the game. Highscores are saved by creating a CRC32 hash of the game data and using that as a key (not part of the script).
Demo:
https://www.dropbox.com/s/b46kn0pfxcanlz5/Sokoban.exe?dl=0
Demo Credits:
(Music) DeceasedSuperiorTechnician (http://www.nosoapradio.us/)
(Graphics) Kenney (http://www.kenney.nl/)
(Scripts) Glitchfinder, Source
Script:
Code:
module IAVRA module GENERATOR # minimum size of a map, smaller maps will be padded and centered. MIN_SIZE = [17, 13] # the character to represent the player position. PLAYER_CHAR = "@" # direction the player is facing. Set to something else than 2, 4, 6, 8 # to retain current facing. PLAYER_DIR = 2 # map chars to tiles (x/y position in the template map). # nil can be used to set a tile for the padding (see MIN_SIZE). TILES = { nil => [0, 0], # floor for padded tiles " " => [0, 0], # floor "@" => [0, 0], # floor under player "$" => [0, 0], # floor under crate "#" => [1, 0], # wall "." => [2, 0], # winning zone "*" => [2, 0] # winning zone under crate } # map chars to events (event id in the template map). EVENTS = { "@" => 1, # player "$" => 3, # crate "*" => 3 # crate on winning zone } # load map #1 as a template, so we can copy tiles and events. MAP_TEMPLATE = load_data("Data/Map001.rvdata2") # initialize tile hash. @@tiles = Hash[TILES.map{|k, v| [k, (0..3).map{|z| MAP_TEMPLATE.data[*v, z]}] }] # initialize event hash. @@events = Hash[EVENTS.map{|k, v| [k, MAP_TEMPLATE.events[v]]}] #-------------------------------------------------------------------------- # creates a map from a text file and teleports the player onto it. #-------------------------------------------------------------------------- def self.load_level_by_file_name(file_name) load_level_by_file_data(parse_file(file_name)) end #-------------------------------------------------------------------------- # similar from the method above, but directly takes the map data. #-------------------------------------------------------------------------- def self.load_level_by_file_data(file_data) data = pad_file(file_data) pos = get_player_pos(data) map = create_map(data) $game_map.setup_directly(map) $game_player.set_direction(PLAYER_DIR) if [2, 4, 6, 8].include?(PLAYER_DIR) $game_player.moveto(*pos) @@last_level_data = file_data end #-------------------------------------------------------------------------- # reloads the last created level. #-------------------------------------------------------------------------- def self.reload_level load_level_by_file_data(@@last_level_data) if @@last_level_data end #-------------------------------------------------------------------------- # loads a file, splits each line into characters and returns the result # as a 2-dimensional array. #-------------------------------------------------------------------------- def self.parse_file(file_name) File.open(file_name) {|f| f.lines.map{|l| l.scan(/./)}} end #-------------------------------------------------------------------------- # Pads an array to center a map that is smaller than the minimal size # defined by MIN_SIZE. Also, maps that are smaller than the default get # displayed pretty strange in RM. #-------------------------------------------------------------------------- def self.pad_file(data) padd_x = [MIN_SIZE[0] - (data.map{|m| m.length}.max || 0), 0].max / 2.0 padd_y = [MIN_SIZE[1] - data.length, 0].max / 2.0 data = data.map{|row| [[nil] * padd_x.floor, row, [nil] * padd_x.ceil].flatten} [[[]] * padd_y.floor, data, [[]] * padd_y.ceil].flatten(1) end #-------------------------------------------------------------------------- # returns the player position as indicated by a defined character. If the # character exists multiple times in the data provided, the first occurence # (from top/left) is returned. #-------------------------------------------------------------------------- def self.get_player_pos(data) data.each.with_index{|row, y| row.each.with_index{|char, x| return [x, y] if char == PLAYER_CHAR }} fail "no player position \"" + PLAYER_CHAR + "\" given." end #-------------------------------------------------------------------------- # creates an instance of RPG::Map from a given data array. calls the methods # create_tiles and create_events to fill the map. #-------------------------------------------------------------------------- def self.create_map(data) width = data.map{|m| m.length}.max map = RPG::Map.new(width, data.length) create_tiles(map, data, width) create_events(map, data, width) map end #-------------------------------------------------------------------------- # get the tile mapped to each character in the given data array from the # template map and store it in map data. characters not included in the # hash are mapped to empty tiles [0, 0, 0, 0]. #-------------------------------------------------------------------------- def self.create_tiles(map, data, width) data.each.with_index{|row, y| (0..width - 1).each{|x| (@@tiles[row[x]] || [0]*4).each.with_index{|tile, z| map.data[x, y, z] = tile} }} end #-------------------------------------------------------------------------- # clones the mapped events and stores them into the map. generates ongoing # ids starting from 1 and sets the event positions accordingly. We also # reset self switches, because all maps are generated with the same id. And # even if we were to generate ids, maps can change between game starts, so # the saved self switches aren't always correct. #-------------------------------------------------------------------------- def self.create_events(map, data, width) index = 0 data.each.with_index{|row, y| (0..width - 1).each{|x| next if @@events[row[x]].nil? attr = {:id => index += 1, :x => x, :y => y} map.events[index] = @@events[row[x]].clone_with_attrs(attr) ("A".."D").each {|s| $game_self_switches[[-1, index, s]] = false} }} end endend#--------------------------------------------------------------------------# ▼ Object#--------------------------------------------------------------------------class Object #-------------------------------------------------------------------------- # clone method, which also sets attributes in the clone. The attributes are # supplied as a hash with the attribute names as symbols for keys. #-------------------------------------------------------------------------- def clone_with_attrs(attr) copy = clone attr.each{|k, v| eval("copy.#{k} = #{v}")} copy end end#--------------------------------------------------------------------------# ▼ Game_Map#--------------------------------------------------------------------------class Game_Map #-------------------------------------------------------------------------- # directly setups a given map instead of loading it from a .rvdata2 file. # Note: Since all maps are generated with the same id, they would all share # the same self switches. To overcome this, self switches for id -1 are reset # everytime a map is generated. #-------------------------------------------------------------------------- def setup_directly(map) @map_id = -1 # default map id @map = map # instead of loading a file @tileset_id = @map.tileset_id @display_x = 0 @display_y = 0 referesh_vehicles setup_events setup_scroll setup_parallax setup_battleback @need_refresh = false end end
Last edited by a moderator:
