Tile Swap

Rycochet

Villager
Member
Joined
Sep 12, 2013
Messages
14
Reaction score
3
First Language
English
Primarily Uses
I've not been using Ruby long enough to know good ways to optimise it yet, but personally I'd not want to have to install any dll to use a script - the way data is stored might be slowing it down slightly, but I've not played with things enough to find out yet... Maybe get a rectangle swap event on a 500x500 map and time it, then find out what can be done to speed things up ;-)

I like the idea of a custom class for one reason - you could have different maps for both the source and dest. The downside is that object creation is just as bad in Ruby as Java - so having nested arrays would be significantly faster in some places... I'm reading up on things right now, but already finding some things that might speed it up slightly (I'll have a fiddle with things overnight  ;) )

Passing in an array with arbitrary points actually makes sense for strange shapes - it's the extra processing that it would entail that would slow things. How about having a Shape class, with effective aliases for Rectangle and Triangle - but the methods can have a shorthand for passing an array, and something quick (point, line or rectangle) would bypass shape creation entirely - so...

pos_swap_from(Point.new(10,10), Rectangle.new(20,10,5,5))...would be semantically almost identical to...

pos_swap_from([10,10], [20,10,5,5])...but use code shortcuts in the second on to save the overhead of object creation... Thinking about it, can even have a single method that just checks for arg type and then the tile id can be numerical, shorthand, positional, or shaped.

Having shapes would be good for things such as replacing the look of a building with another that takes the same space, but you don't want to use regions...

Speaking of regions - might be nice if there was a setting/flag that let you also copy the region data from the dest (if using shapes), and possibly even a mask that let you copy a shape but skip any tiles matching xyz (linked to copying regions etc).

Edit: Have one major speed boost for Game_Map at least - reduces the update to once per frame, so having a lot of updates is almost instant - next need to streamline the replacement code a little...

Code:
  #-----------------------------------------------------------------------------  # New. Load custom map data on top of our map  #-----------------------------------------------------------------------------  def load_new_map_data    @need_load_new_map_data = true  end		  #-----------------------------------------------------------------------------  # Aliased. Refresh the map if we've got pending changes  #-----------------------------------------------------------------------------  alias :tsuki_tile_swap_update_map :update  def update(main = false)    if @need_load_new_map_data      @need_load_new_map_data = false      @updated_tiles = {}      swap_tile_id      swap_tile_region      swap_tile_pos      expand_updated_tiles      update_all_autotiles    end    tsuki_tile_swap_update_map(main)  end
 
Last edited by a moderator:

Tsukihime

Veteran
Veteran
Joined
Jun 30, 2012
Messages
8,564
Reaction score
3,846
First Language
English
Most of the script was written 3 weeks after I started RM scripting and I don't remember anything about why I chose to write certain things a certain way. Version 2 only introduced the new "tile ID" concept with letters and numbers, as well as the autotile code from killozapit.


I should probably go back and review the code to see if there are any obvious issues.


Updating the tiles everytime a swap is called is definitely a questionable decision.


I have updated the script with that optimization.


Another possible optimization is to avoid performing all of the swaps.


For example, if I perform a single pos swap, do I really need to check for region swaps? Probably not


I have not checked it closely but I think if you swapped say 100 tiles, then everytime you update the map, it will set those same 100 tiles again and again even if they were not modified. Additional flags can be added to minimize the amount of swap operations being performed since looping over a bunch of arrays can get slow.


As for overhead from object creation, I would imagine there is overhead for creating an array as well (how much more or less? I am not sure how to find out).


However in terms of user-friendliness, it may be more sensible to just stick with arrays because those are "common enough"
 
Last edited by a moderator:

Rycochet

Villager
Member
Joined
Sep 12, 2013
Messages
14
Reaction score
3
First Language
English
Primarily Uses
I started learning Ruby 3 days ago, so that's good lol

I've been using Benchmark, that first change there took it from 3+ seconds to change every tile in a 20x20 map, to 0.3 seconds to change every tile in a 200x200 map.

I'm about to try some changes to make marking tiles as changed use a bitfield as that should take time down significantly for part of it - here's some timings for the 200x200 map, changing between A17 and A25, the entire map is changed for each one -

user system total realpos_swap 0.109000 0.000000 0.109000 ( 0.117006) user system total realswap_tile_id 0.000000 0.000000 0.000000 ( 0.000000)swap_tile_region 0.000000 0.000000 0.000000 ( 0.000000)swap_tile_pos 0.031000 0.000000 0.031000 ( 0.025002)expand_updated_tiles 0.359000 0.000000 0.359000 ( 0.364021)update_all_autotiles 0.405000 0.000000 0.405000 ( 0.395022) user system total realrevert_all 0.000000 0.000000 0.000000 ( 0.002000) user system total realregion_swap 0.000000 0.000000 0.000000 ( 0.000000) user system total realswap_tile_id 0.000000 0.000000 0.000000 ( 0.000000)swap_tile_region 0.202000 0.000000 0.202000 ( 0.197011)swap_tile_pos 0.000000 0.000000 0.000000 ( 0.000000)expand_updated_tiles 0.094000 0.000000 0.094000 ( 0.088005)update_all_autotiles 0.094000 0.000000 0.094000 ( 0.096006) user system total realrevert_all 0.015000 0.000000 0.015000 ( 0.002000) user system total realtile_swap 0.000000 0.000000 0.000000 ( 0.000000) user system total realswap_tile_id 0.031000 0.000000 0.031000 ( 0.033002)swap_tile_region 0.000000 0.000000 0.000000 ( 0.000000)swap_tile_pos 0.000000 0.000000 0.000000 ( 0.000000)expand_updated_tiles 0.094000 0.000000 0.094000 ( 0.088005)update_all_autotiles 0.093000 0.000000 0.093000 ( 0.097005) user system total realrevert_all 0.000000 0.000000 0.000000 ( 0.001000)
Edit: And got bitfields for the tiles done, again - major speedups - expand_updated_tiles now takes negligible time, and update_all_autotiles hasn't changed - http://pastebin.com/sjzaC2qg

This is strange - for some reason update_all_autotiles is significantly slower when a pos_swap of the entire map is done, and yet exactly the same tiles are changed - this happens even on the original code, just can't quite see what's causing that yet - is it the other methods skipping things, or is it the pos_swap doing too much... Anyway, attached my test map here if anyone wants to have a look...

Edit2: Accidentally forgot to increase the region size and flood-fill the new area when I went from 100x100 to 200x200 - timings are now as expected... Still refactoring and getting some nice speed increases.
 
Last edited by a moderator:

Rycochet

Villager
Member
Joined
Sep 12, 2013
Messages
14
Reaction score
3
First Language
English
Primarily Uses
Ok, got some speed improvements and partially refactored.

My test map (included at the end) is 500x500 (the maximum normal map size), and on my machine takes about 3.3 seconds to update the map completely (2.3 seconds is the autotile checking, I've also got a powerful machine, so expect it to take longer if yours isn't relatively new).

Benchmarks:

Map: 500 x 500 = 250000 tiles user system total realtile_swap 0.000000 0.000000 0.000000 ( 0.000000)region_swap 0.000000 0.000000 0.000000 ( 0.000000)pos_swap 0.437000 0.000000 0.437000 ( 0.437025)Game_Map.update() user system total realswap_tile_id 0.406000 0.000000 0.406000 ( 0.398023)swap_tile_region 0.390000 0.000000 0.390000 ( 0.394023)swap_tile_pos 0.234000 0.000000 0.234000 ( 0.223012)expand_updated_tiles 0.000000 0.000000 0.000000 ( 0.000000)update_all_autotiles 2.277000 0.000000 2.277000 ( 2.282130)
Code: http://pastebin.com/sjzaC2qg

I'm sure there's going to be more time savings to be made, and I really ought to do something about comparing the speeds of the old and new code too ;-)

Tile Swap.zip
 

Attachments

Tsukihime

Veteran
Veteran
Joined
Jun 30, 2012
Messages
8,564
Reaction score
3,846
First Language
English
Updating 250k tiles in 5 seconds is impressive. Which is more like about 2M tiles for the autotile expansion, since everytime a tile is updated, you need to re-check adjacent tiles so they can reflect the new tile. eg: if you have a tile x1 and then place another tile x2 on the left, you need to check x1 for updates. Then if you place another tile x3 on the right, you need to check x1 again for updates. This can be minimized by performing the tile swaps in a "smart" order like interleaving or row-by-row or column-by-column, but that would require the tiles to be sorted.


I have updated the script with the changes you've made.


Initially I didn't want to do any "heavy" tile swapping because it would be too slow, but at this point you could probably swap any number of tiles and players wouldn't notice any lag.
 
Last edited by a moderator:

Rycochet

Villager
Member
Joined
Sep 12, 2013
Messages
14
Reaction score
3
First Language
English
Primarily Uses
I actually swapped in the old script and ran it, the pos_swap has been running for the past 7 hours, and still not finished marking tiles as swappable - so I quit it lol... The tile and region both took around 6.5 seconds - so quite a speedup lol

I like how it does that check for the autotiles - I've improved it by using a bitfield (finding out about Fixnum and Bignum in the process) - but it basically makes a blur mask of everything that's already been changed, so only writes to each changed tile once - it does read up to every tile around the changed tile depending on if it's a full, wall, or waterfall autotile (I test with full autotiles, but walls take 1.7s and waterfalls take 0.9s - so that is the slowest part of the code now... Hrm... I have an idea lol).

I have been wishing that Ruby had true threading, as that would help - will probably try testing things later, but it's highly unlikely that we've got it to use (until recently only one version of Ruby had true threading, most use "green" threading, which just shares time on one core).

Wanting to add shapes and tile buffers next - so you can copy from other maps and create in-memory maps for swapping out in one go. Don't know if you noticed the change to convert_tid() - but anywhere that takes a "A13" tile can now take a [x,y] coordinate - I have been wondering if/how to do range copies, but not come up with anything quite yet, got some ideas at the edge of my thoughts, but nothing that seems usable yet... I don't like the idea of adding a lot of extra functions that might not be "right" iykwim.

Edit:

Well, my idea panned out, more than double the speed again, compare these benchmarks to my ones up above:

Map: 500 x 500 = 250000 tiles user system total realtile_swap 0.000000 0.000000 0.000000 ( 0.000000)region_swap 0.000000 0.000000 0.000000 ( 0.000000)pos_swap 0.390000 0.016000 0.406000 ( 0.441026)convert_tid 0.218000 0.000000 0.218000 ( 0.223012)Game_Map.update() user system total realswap_all 0.266000 0.000000 0.266000 ( 0.270016)update_all_autotiles 1.014000 0.031000 1.045000 ( 1.446083)
Code is at http://pastebin.com/sjzaC2qg as usual, I've also updated my test map too. The convert_tid method is still pretty slow, but 250k calls in 0.2s isn't that bad (shaped destinations would remove that issue) - next up will be adding functionality ;-)

Tile Swap.zip
 

Attachments

Last edited by a moderator:

Tsukihime

Veteran
Veteran
Joined
Jun 30, 2012
Messages
8,564
Reaction score
3,846
First Language
English
The main script has been updated with the optimizations.

I've been thinking of something like this for the convert_tid

class Game_System # interface method def convert_tid(obj, layer=0) return obj.convert_tid(layer) endend## All objects define how it will be done#class String def convert_tid(layer) #... end endclass Array def convert_tid(layer) return $game_map.tile_id(self[0], self[1], layer) endendclass Point attr_accessor :x attr_accessor :y def convert_tid(layer) return $game_map.tile_id(@x, @y, layer) endendclass Rectangle attr_accessor :x attr_accessor :y attr_accessor :width attr_accessor :height def convert_tid(layer) #... endend
Which allows you to provide the tile swap as an interface and then implement various shapes or objects.

Several other functions will be converted to interfaces as well.

I would like the actual swapping to be performed in the main class as a private function, and not something other shapes need to be concerned with. They only need to indicate which tiles need to be swapped, and then hand it over to the tile swap framework to handle the rest.

This may not be the most efficient approach (cause having a rectangle perform a rectangular swap is probably more efficient than performing a general swap), but my goal is to make it easy to define your own swap rules and not have to worry about how to do the swapping yourself.
 
Last edited by a moderator:

Rycochet

Villager
Member
Joined
Sep 12, 2013
Messages
14
Reaction score
3
First Language
English
Primarily Uses
Hrm, I think convert_tid is only needed to get a single tile value - the only exception would be a shape from the tileset (ie, a 1x2 door etc - maybe treat the tileset like a 8px wide image in terms of coordinates?)

Saying that - having a shape for either a from or two place, that doesn't rely on a region, would work well as you have it with only a single change - no need for coordinates within the shape, as that would be at placement time (and would mean it could be used as a stamp pretty easily). I'd suggest using something like the bitfield code I've put together in there as it's quick and memory efficient - how about something like:

class Shape # include all the bitfield codeclass Rectangle < Shapenew Rectangle(width, height=width) # square or rectanglenew Circle(width, height=width) # circle or ellipseOther shapes could be added relatively easily, and you could use Shape itself to manually "draw" a shape... Actually, not sure if it isn't just worth having a plain Shape class, with methods to create shapes within it - then it could be used as a single shape, or with multiple calls for drawing something -

box = Shape.rectangle(width, height)complex = new Shape(max_width = $map.width, max_height = $map_height)complex.rectangle!(x, y, width, height)complex.circle!(x, y, width)complex.region!(id, layer = 0, mapid = $map.id)complex.tile!(tid, layer = 0, mapid = $map.id)complex.shift(down, right) # negative for up/left...Then when copying either source or dest can be a shape, but not both (maybe later? Would make some seriously weird maps without the ability to resize tiles lol)

I've got most of the swapping code down into a small number of functions - the only reason there aren't fewer is that it's easier to benchmark things with them that way...

Hrm - technically the current updated_tiles stuff uses plain bitfields too - could relatively easily change that into a Shape - then add the "grow" method to it - would be very slightly slower, but it'd also keep things really clean...

Oh, and from your earlier post, it now only actually swaps a tile once - checks position first, else region, else tile - sped things up a little more ("fake" multidimensional arrays as objects are slow, coincidentally that's what $map.data uses lol).

Hrm...

Think I like my second idea - could be easily used as the intermediary for the current code, with only a very slight performance penalty I think...
 

Tsukihime

Veteran
Veteran
Joined
Jun 30, 2012
Messages
8,564
Reaction score
3,846
First Language
English
Yes, convert_tid's purpose is to convert the human-friendl(ier) tile ID into the unfriendly internal tileset code.


Probably a bad example.


I have not thought about complex tile swapping since the script was originally written to simply replace one tile with another tile, but let's see what kind of things you can come up with.
 
Last edited by a moderator:

Rycochet

Villager
Member
Joined
Sep 12, 2013
Messages
14
Reaction score
3
First Language
English
Primarily Uses
Ack, just noticed a typo on line 328 - "swap_tile_all" should be "swap_all", my fault for manually editing out my test code and not testing the untested version!
 

Dark_Metamorphosis

What a horrible night to have a curse.
Veteran
Joined
Nov 23, 2012
Messages
2,192
Reaction score
382
First Language
Swedish
Primarily Uses
I totally love this script! So far I'm using it for one of my dungeons, where you have to drain the water level to find items that you need to progress. You will also have to fill the water back again in order to complete puzzles and other stuff, it works so nice for what I have in mind.

I'm also using it to change the enviorment (a house in my game is burning, and after a while it will be burnt to the ground etc) There are so many ways that you can use this script, and it was completely impossible to change the enviorment before I found this script!

In other words, It's awesome!
 
Last edited by a moderator:

Rycochet

Villager
Member
Joined
Sep 12, 2013
Messages
14
Reaction score
3
First Language
English
Primarily Uses
Aaaaand 3.0b1 is ready - http://pastebin.com/sjzaC2qg

I'de added a Map_Mask class, no documentation outside the source code (don't worry, I believe in comments, so it's not completely obscure) - download the demo if you want an example, there's teleport doors to other tests, but the start area you can just keep hitting the green button to shrink the water slowly (animation stuff in the future?)

Why is it a beta? Well, despite working identically and fixing several accidental bugs I'd introduced (and some older ones I discovered), there's major changes in it so it needs a new version - however it still needs at least one extra ability (the tile "A17" style id needs to be able to automatically set the layer as some tiles are specific - http://forums.rpgmakerweb.com/index.php?/topic/2829-autotile-tile-ids/) and it does need some improved and updated documentation ;-)

Current benchmark speeds put it at about 1.4s for a full 500x500 map update, no matter how much stuff is changed on it - that's the limit in map size of RPGMaker VX Ace (total map tiles, not in any dimension).

Tile Swap.zip
 

Attachments

Tsukihime

Veteran
Veteran
Joined
Jun 30, 2012
Messages
8,564
Reaction score
3,846
First Language
English
Tested and updated.


The new demo map is very nice (and a lot more practical than the one in my screenshot).
 
Last edited by a moderator:

Dark_Metamorphosis

What a horrible night to have a curse.
Veteran
Joined
Nov 23, 2012
Messages
2,192
Reaction score
382
First Language
Swedish
Primarily Uses
Hi Tsukihime!

I hope It's okay that I post about a problem I'm having with the script here.

So yesterday I made my third puzzle for my game, and I'm using your script for several events in my game (including this puzzle that I'm talking about)

So during the puzzle, the player must drop down 2 boxes from an upper area down into a lower area, and when doing this he will form a small bridge over some water tiles that were impassable before. This is one of many reasons why I love this script, since if I would have used events to display the boxes (once they have been droped from the upper floor) there's no way that I can take another event and make it cross over this small bridge of boxes (since the water tile is not walkable).

So I decided to use your lovely script again and added the events with swap tiles script calls, and tada I can now cross the bridge with another event.

The problem I have though is that when I try to reset the puzzle (There will be a lever that the player can interact with to reset the puzzle, in case they mess up) then I just can't revert those tiles and make them dissapear (so that box bridge will dissapear, and the water will be back again).

This is the event where I swap in the tiles:

Works like it should and the box tiles are being displayed as it should.

But when I try to reset the puzzle there's no way I can make them go away. It feels like I have tried everything, region id swap with the water tile instead, pos_swap with the water tile instead.

I have also tried all the revert calls, but they just won't go away.

This is the call I use inside the reset event:

I even tried to reset by map id, but they just wont go away.

The puzzle is being reset as it should otherwise, It's just these two tiles that won't disappear.

Any suggestions?

Thanks!

~Dark.
 

Rycochet

Villager
Member
Joined
Sep 12, 2013
Messages
14
Reaction score
3
First Language
English
Primarily Uses
There's a slight update on the pastebin (http://pastebin.com/sjzaC2qg) which has auto-layer if you're not trying anything funny (like autotiles on non-background layers etc)...

The one thing I'll say from that code is that you need to use the relevant revert_* call - as you're swapping with pos_swap, you need to use pos_revert -

pos_swap(18, 4, "D32", 2, 89)pos_revert(18, 4, 2, 89)revert_all(89)I'm planning on changing the args and calls to make it harder to do things the wrong way like this - you've included the tile id that's being swapped, which doesn't actually get checked anywhere, so is trying a comparison against the wrong thing (ie, there's no type-checking in Ruby, and you've hit on that).

Hope that makes sense and fixes your problem there :)
 

Dark_Metamorphosis

What a horrible night to have a curse.
Veteran
Joined
Nov 23, 2012
Messages
2,192
Reaction score
382
First Language
Swedish
Primarily Uses
Ohhh! Of course! You need to set the proper swap command used before the _revert command, that's where I did it all wrong and instead just used 'tile'..

I'll make sure to fix that and see if it works this time around, thanks a lot!

I'll make sure to use the new updated one as well, since it seems like there have been a lot of fixes to it! An awesome script that's getting even more awesome is just fantastic :')

Edit: Nevermind that, do I need to use all of those commands that you included in the code box?

I thought that each of those would revert different kinds of swaps?

Like region_revert would change the tiles in the specific region ID and the revert_all would reverse every swap that has been made on the map? I'm a bit unsure how it works now again, but do I need to add the pos_swap as well before adding the revert code?

Oh, and I get a syntax error from the new updated script @ line 727: Invalid next.

Edit again: Script error gone now, so it works. when I use the script call: pos_revert(18, 4, "D32", 2, 89)

though, I get an argument error; wrong number of arguments (4 for 5)
 
Last edited by a moderator:

Rycochet

Villager
Member
Joined
Sep 12, 2013
Messages
14
Reaction score
3
First Language
English
Primarily Uses
Sorry, missed your edits after reading the reply -

For the wrong number of arguments: you don't pass in the same args to the revert as the add - if you look at my example the "D32" is only there when creating the swap, not when reverting it - it's stored relative to what is swapped, not what it is swapped to... You can also skip the map and layer args if you're doing them on the current map as those details are automatic (the map always chose the current one if not specified anyway)

Revert removes any changes you've made and puts the original ones back. If you have lots of separate changes then revert_all will remove all of them. In future you'll be able to revert a subsection of changes, at the moment it's just "all or one" type reversion. It will be easier with the next update (just got to finish the comments and descriptions, then make sure I test thoroughly).
 

Dark_Metamorphosis

What a horrible night to have a curse.
Veteran
Joined
Nov 23, 2012
Messages
2,192
Reaction score
382
First Language
Swedish
Primarily Uses
Sorry for all the mess. I finally got it to work :)

The fault was on my part. I had the event to reset all the switches and enable the revert command before traveling to the new map (which I use to reset the positions of all the events). The script call would still work and remove the tiles, but once I transfered to the new map and back again to reset all the events, the tiles that was reverted would appear again. I then had to press the switch one more time in order for it to work.

So I switched it up a bit and made the transfer before initiating the script call, so It will remove them after the transfer. Works great now! I'm not sure why they appeared again the first time I used the switch, but I guess It's because the events are being reset once you transfer. In any case I will use it like this in the future if I need to. There won't be many cases where I will need to set up an event like this anyway.

Looking forward to the new release!
 

Rycochet

Villager
Member
Joined
Sep 12, 2013
Messages
14
Reaction score
3
First Language
English
Primarily Uses
Actually the tiles are remembered per map - so if you swap maps and back again they'll still be changed - but if you quit then reload it'll be forgotten unless you do something about it deliberately (hrm, should probably do something about that lol).

Just realised one other change I need to make, still writing up documentation stuff - just my partner's first week back at uni so not very much time - I do have tomorrow free for games and coding though ;-)
 

bobisme

Veteran
Veteran
Joined
Apr 25, 2013
Messages
112
Reaction score
4
First Language
english
Primarily Uses
hey guys, noob question :p

i want  room/s of a map to be hidden with a black tile and then when they open the door the room becomes visible like it would on chrono trigger, would this script do that? :)

or is it a swap one tile for one tile, i noticed further up the door mechanic (which looks great) but it would require a tile by tile change? so a script 'statement' for each tile?

here's my room in question and what i want them to do, as you can see the rooms open up 'as you enter them'

so as each door is opened the tile swap initiates.

no visible rooms


hiddenr2 by Therealbobisme, on Flickr
1 visible room


hiddenr3 by Therealbobisme, on Flickr
2 visible rooms


hiddenr4 by Therealbobisme, on Flickr
and so on..

there are alot of rooms, 

is there something more to my requirements like a room swap? :p

if there is i can't find it :p
 
Last edited by a moderator:

Users Who Are Viewing This Thread (Users: 0, Guests: 2)

Latest Threads

Latest Profile Posts

People3_5 and People3_8 added!

so hopefully tomorrow i get to go home from the hospital i've been here for 5 days already and it's driving me mad. I miss my family like crazy but at least I get to use my own toiletries and my own clothes. My mom is coming to visit soon i can't wait to see her cause i miss her the most. :kaojoy:
Couple hours of work. Might use in my game as a secret find or something. Not sure. Fancy though no? :D
Holy stink, where have I been? Well, I started my temporary job this week. So less time to spend on game design... :(
Cartoonier cloud cover that better fits the art style, as well as (slightly) improved blending/fading... fading clouds when there are larger patterns is still somewhat abrupt for some reason.

Forum statistics

Threads
105,868
Messages
1,017,083
Members
137,583
Latest member
write2dgray
Top