Creating Maps from Text files (incl. Demo: Sokoban)

Iavra

Veteran
Veteran
Joined
Apr 9, 2015
Messages
1,797
Reaction score
866
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:

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:

KanaX

Just being a sleepy
Veteran
Joined
Apr 3, 2013
Messages
1,455
Reaction score
1,299
First Language
Broken English.
Primarily Uses
N/A
If I understand correctly how this script works, it will do WONDERS for games with many similar-use maps, like arcade-like games with many levels. On the same time this might mean that it might go unnoticed on the forums, since not many of us choose to develop those kind of games. It can be life-saving though!

Edit: You basically said the exact same thing on your post, sorry for not noticing.
 
Last edited by a moderator:

Latest Threads

Latest Posts

Latest Profile Posts

and_remember_tomorrow_beside_myself.jpg

The worst part about making custom stuff is, I can't really ask any of you why a second copy of the character randomly appears only when going SW, S, or SE and changing directions. Been bugging me for the last two days. On the plus side, look at the new random map pieces I made while thinking about it.:LZSexcite:
RPG Maker News #11 | Mech Battlers, Angels of Death on Console, New MZ Plugins & Music Packs

forgot I drew Riley's message bust ages ago. They might be one of the most unique characters in the game so far.
37 years old...
What hath possessed me to start rapping, Lloyd only knows.

I think the worst part is, I am actually really good...

no... actually, the worst part is writing a song about bum rushing Gandalf to stop Frodo...
I don't even listen to rap.
Just finished v0.1 of FOSSIL. It's a plugin that lets you use MV plugins in MZ without editing them by making them use new functions.

I have about 40 plugins tested and working so far (including chrono engine and some other big ones). I hope that the community will pitch in and help improve it, so we can use all the old plugins in MZ. :)

Forum statistics

Threads
110,430
Messages
1,053,226
Members
143,496
Latest member
bk8vnmobi
Top