Jump to content

[TOPIC: topicViewTemplate]
[GLOBAL: userSmallPhoto]
Photo

How to create a Platformer
Started by redninjacat Feb 19 2016 07:38 AM

* * * * - 2 votes
49 replies to this topic
platformer tiled tiles platform
[TOPIC CONTROLS]
Page 2 of 2 1 2
This topic has been archived. This means that you cannot reply to this topic.
[/TOPIC CONTROLS]
[modOptionsDropdown]
[/modOptionsDropdown]
[reputationFilter]
[TOPIC: post.html]
#26

Caleb P

[GLOBAL: userInfoPane.html]
Caleb P
  • Corona Geek

  • 1,424 posts
  • Corona SDK

Hi @jerejigga:

In a bit I'll be releasing Dusk 1.0, and there will be some more complex non-Box2D physics samples (Link in a platformer, anyone?) in it. My physics engine was a commission from a Corona developer that I was paid for, so I don't really feel comfortable sharing it with everyone at this time. When the Dusk samples come, though, there will be plenty of physics behaviours to hack and examine.

 

Also, @thomas6's guide looks good!

 

- Caleb



[TOPIC: post.html]
#27

thomas6

[GLOBAL: userInfoPane.html]
thomas6
  • Contributor

  • 976 posts
  • Corona SDK

Hi!

 

Always willing to share my platform physics code. Been wanting to do a tutorial for a loooooooooooooong time about that.



[TOPIC: post.html]
#28

redninjacat

[GLOBAL: userInfoPane.html]
redninjacat
  • Contributor

  • 196 posts
  • Corona SDK

@CalebP

 

I totally understand!! As a side note, I think it's awesome that you are doing paid work already! My first paid development work was in the area of $1 per game. I imagine you are getting a lot more than that! :) You do great work and totally deserve it.

 

Regarding Dusk 1.0, I am very much looking forward to it! Like I've said already, most of my focus so far has been on MTE. Presently though, I am soaking up general information on Tiled based games including the amazing info in this forum topic. Once Dusk 1.0 comes out, I plan to jump right in! I am very much looking forward to the revised API, improved documentation, and the "physics" samples you just referred to.

 

@thomas6

 

Thank you for sharing the PDF!! Obviously it's based on Flash, but DANG! This PDF contains the best documentation on creating a tiled-based game that I have ever seen! I just read the first few pages so far and scanned the rest but it looks quite thorough. I am primarily a Java developer and have done lots of JavaScript. Flash's ActionScript looks very similar so I should have no problem adapting this to Corona/Lua. Awesome stuff!



[TOPIC: post.html]
#29

thomas6

[GLOBAL: userInfoPane.html]
thomas6
  • Contributor

  • 976 posts
  • Corona SDK

Hi Caleb (and others),

 

Yes, that PDF is pretty awesome, I know ;)



[TOPIC: post.html]
#30

redninjacat

[GLOBAL: userInfoPane.html]
redninjacat
  • Contributor

  • 196 posts
  • Corona SDK

@thomas6 You mentioned sharing your platforming physics code. Is that something you could do right now or is that in regard to writing a tutorial and, presumably, doing some clean-up/refactoring first? If it's something you can share now, I for one would certainly love to have access to it, tutorial or not. I love looking at other people's code to see how they do things. I am sure that many other people in this community would really enjoy and appreciate seeing such code too.



[TOPIC: post.html]
#31

thomas6

[GLOBAL: userInfoPane.html]
thomas6
  • Contributor

  • 976 posts
  • Corona SDK

Hold on. I just posted my code, but decided to delete it because it was way too chaotic (and the code formatting was all over the place). Let me do a quick clean up first. Be right back.



[TOPIC: post.html]
#32

thomas6

[GLOBAL: userInfoPane.html]
thomas6
  • Contributor

  • 976 posts
  • Corona SDK

Okay. Here's a big chunk. Not sure if it'll be of any help without explanation, but if you'd like to check it out, go ahead and feel free to ask questions:

hero.frameLoop = function(event)
	
	local touching = touching
	
	-- compute potential horizontal motion for next frame
	--print(editor.state)
	if hero.dead == false then
		if touching == "left" then
			hero.xVel = hero.xVel - hero.xAccel
		elseif touching == "right" then
			hero.xVel = hero.xVel + hero.xAccel
		elseif touching == false then
			hero.xVel = hero.xVel * hero.friction
		end
	end
	
	if hero.xVel > hero.xMaxVel then
		hero.xVel = hero.xMaxVel
	elseif hero.xVel < -hero.xMaxVel then
		hero.xVel = -hero.xMaxVel
	end
		
	hero.xPosNext = hero.xPos + hero.xVel
	if hero.platform ~= false then
		-- add platform xOffset to hero.xPosNext
		hero.xPosNext = hero.xPosNext + platformList[hero.platform].xOffset
	end
	
	-- compute potential vertical motion for next frame
	if hero.jumpState == "falling" then
		hero.yVel = hero.yVel + hero.yAccel
		jumpTouch = false
	elseif hero.jumpState == "grounded" then
		if jumpTouch == true then
			jumpTouch = false
			hero.jump()
		end
	end
	
	
	if hero.yVel > hero.yMaxVel then
		hero.yVel = hero.yMaxVel
	elseif hero.yVel < -hero.yMaxVel then
		hero.yVel = -hero.yMaxVel
	end
	
	-- set yPosNext
	if hero.platform == false then
		-- move hero with velocity
		hero.yPosNext = hero.yPos + hero.yVel
	else
			-- move along with platform
		hero.yPosNext = platformList[hero.platform].image.y
	end	
	
	-- reevaluate next position for hero based on collisions!
	local levelMap = levelMap
		-- first correct the vertical movement, based on current X and next Y position !!!
	local bottomRow = math.floor((hero.yPosNext)/94) -- first row = row 0 which is good for row multiplier 0*mapwidth for Arraypos's
	local topRow = math.floor((hero.yPosNext-252)/94) -- first row = row 0 which is good for row multiplier 0*mapwidth for Arraypos's
	local leftColumn = math.floor((hero.xPos-62)/126) -- first column = column 0 so add 1 for Arraypos's
	local rightColumn = math.floor((hero.xPos+62)/126) -- first column = column 0 so add 1 for Arraypos's	
	if (hero.jumpState == "falling") or (hero.platform ~= false) then
		if hero.yVel > 0 then -- moving down
			-- check if bottom points are in a platform
			if hero.platform == false then
				for i = 1, #platformList do
					local vDistance = hero.yPosNext - platformList[i].image.y
					local hDistance = math.abs(hero.xPosNext - platformList[i].image.x)
					if (vDistance > 0) and (vDistance < 64) then -- 64 for platform height
						if platformList[i].type == 3 then
							if (hDistance < 236) then
								hero.jumpState = "grounded"
								hero.platform = i
								hero.yPosNext = platformList[i].image.y
								if platformList[i].dropform == true then
									platformList[i]:startTimer()
								end
							end
						elseif platformList[i].type == 2 then
							if (hDistance < 156) then
								hero.jumpState = "grounded"
								hero.platform = i
								hero.yPosNext = platformList[i].image.y
								if platformList[i].dropform == true then
									platformList[i]:startTimer()
								end
							end
						end
					end -- if
				end -- for platformList
			end
			
			-- repeat check for bottom points in a dropform!!!

		
			local botLeftTile = levelMap.physics[leftColumn+1+bottomRow*levelMap.width]
			local botRightTile = levelMap.physics[rightColumn+1+bottomRow*levelMap.width]
			
			if (botLeftTile ~= 1) or (botRightTile ~= 1) then
				-- one or more bottom corners will be on a solid tile
				audio.play(landSound)
				hero.yVel = 0
				hero.yPos = (math.floor(hero.yPosNext/94)*94)-1
				hero.jumpState = "grounded"
				hero.platform = false
			else
				hero.yPos = hero.yPosNext
			end
			
		elseif hero.yVel < 0 then -- moving up
			local topLeftTile = levelMap.physics[leftColumn+1+topRow*levelMap.width]
			local topRightTile = levelMap.physics[rightColumn+1+topRow*levelMap.width]				
				if (topLeftTile == 2) or (topRightTile == 2) then	
				-- one or more top corners will be on a solid tile
				hero.yVel = 0
				-- WRONG!!! 
				hero.yPos = (math.floor(hero.yPosNext/94)*94)+64
			else
				hero.yPos = hero.yPosNext
			end
		elseif hero.yVel == 0 then -- no vertical movement	
			hero.yPos = hero.yPosNext
			
		end -- if (hero.jumpState == "falling" or platform is true)
	else -- if hero == "grounded" 
	
	end
	
	-- then correct the horizontal movement, based on corrected Y and next X position !!!
	bottomRow = math.floor((hero.yPos)/94) -- first row = row 0 which is good for row multiplier 0*mapwidth for Arraypos's
	local midRow = math.floor((hero.yPos-126)/94) -- first row = row 0 which is good for row multiplier 0*mapwidth for Arraypos's
	topRow = math.floor((hero.yPos-252)/94) -- first row = row 0 which is good for row multiplier 0*mapwidth for Arraypos's
	leftColumn = math.floor((hero.xPosNext-62)/126) -- first column = column 0 so add 1 for Arraypos's
	rightColumn = math.floor((hero.xPosNext+62)/126) -- first column = column 0 so add 1 for Arraypos's	
	
	local xSpeedWithPlatform = hero.xVel 
	if hero.platform ~= false then
		xSpeedWithPlatform = hero.xVel + platformList[hero.platform].xOffset
	end
	
	if xSpeedWithPlatform > 0 then -- moving right
		local topRightTile = levelMap.physics[rightColumn+1+topRow*levelMap.width]
		local midRightTile = levelMap.physics[rightColumn+1+midRow*levelMap.width]
		local botRightTile = levelMap.physics[rightColumn+1+bottomRow*levelMap.width]
		
		if (topRightTile == 2) or (midRightTile == 2) or (botRightTile == 2) then
			-- one or more right corners will be on a solid tile
			-- check wether on platform and only bottom point colliding
			if (hero.platform ~= false) and (topRightTile ~= 2) and (midRightTile ~= 2) then
				-- hitting only bottom point to get off platform!
				hero.xPos = hero.xPosNext
			else
				hero.xVel = 0
				hero.xPos = (math.floor(hero.xPosNext/126)*126)+63
			end
		else
			hero.xPos = hero.xPosNext
		end
		
	elseif xSpeedWithPlatform < 0 then -- moving left
		
		local topLeftTile = levelMap.physics[leftColumn+1+topRow*levelMap.width]
		local midLeftTile = levelMap.physics[leftColumn+1+midRow*levelMap.width]
		local botLeftTile = levelMap.physics[leftColumn+1+bottomRow*levelMap.width]			
			if (topLeftTile == 2) or (midLeftTile == 2) or (botLeftTile == 2) then
			-- one or more top corners will be on a solid tile
			-- check wether on platform and only bottom point colliding
			if (hero.platform ~= false) and (topLeftTile ~= 2) and (midLeftTile ~= 2) then
				-- hitting only bottom point to get off platform!
				hero.xPos = hero.xPosNext
			else
				hero.xVel = 0
				hero.xPos = (math.floor(hero.xPosNext/126)*126)+64
			end
		else
			hero.xPos = hero.xPosNext
		end
	elseif hero.xVel == 0 then -- no horizontal movement	
			hero.xPos = hero.xPosNext
	end
	
	-- gate check here!
	for i = 1, #gateList do
		local gate = gateList[i]
			if gate.state == "closed" then
			-- start checking stuff
			-- if gate is OPEN we do nothing at all! :-)
			
			-- CHECK wether hero is IN gate rect
			if math.abs(gate.group.x - hero.xPos) < 128 then
				-- in horizontal area so check V overlap
				if (gate.group.y - hero.yPos) < 192 and (gate.group.y - hero.yPos) > - 444 then					
					-- Y also within gate
					if hero.holdingKey == false then
						-- hero is blocked because gate is closed and hero is not holding a key
						-- BLOCK HERO!!!
						if hero.xVel < 0 then
							-- moving left
							hero.xVel = 0
							hero.xPos = gate.group.x + 128
						elseif hero.xVel > 0 then
							-- moving right
							hero.xVel = 0
							hero.xPos = gate.group.x - 128
						end
					else
						-- HERO is HOLDING A KEY!
						-- So check if it is the RIGHT KEY index for this gate
						if keyList[hero.holdingKey].group.index == gate.group.index then
							-- hero opens door and moves through
							gate.open(gate) -- is equal to gate:open()
							keyList[hero.holdingKey].group.y = -1000
							keyList[hero.holdingKey].state = "used"
							hero.holdingKey = false
						else
							-- hero is holding a key with the wrong index
							-- BLOCK HERO!!!
							if hero.xVel < 0 then
								-- moving left
								hero.xVel = 0
								hero.xPos = gate.group.x + 128
							elseif hero.xVel > 0 then
								-- moving right
								hero.xVel = 0
								hero.xPos = gate.group.x - 128
							end
						end -- check if holding the right key
					end	-- hero holding key or not check ends
				end -- if hero is in vertical rect check ends
			end -- if hero is in horizontal rect check ends
		end	-- if gate.state == CLOSED or OPEN check ends
	end -- for loop over all gates ENDS
	
			
	-- finally, check to see if hero is still standing on solid tiles or on finish
	-- if not on solid or cloud, set jumpState to "falling"
	-- if on finish tile, level is completed!
	bottomRow = math.floor(((hero.yPos)/94)+1) -- first row = row 0 which is good for row multiplier 0*mapwidth for Arraypos's
	leftColumn = math.floor((hero.xPos-62)/126) -- first column = column 0 so add 1 for Arraypos's
	rightColumn = math.floor((hero.xPos+62)/126) -- first column = column 0 so add 1 for Arraypos's		
		local botLeftTile = levelMap.physics[leftColumn+1+bottomRow*levelMap.width]
	local botRightTile = levelMap.physics[rightColumn+1+bottomRow*levelMap.width]		
		if hero.platform ~= false then -- hero is standing on a platform
		local hDistance = math.abs(hero.xPosNext - platformList[hero.platform].image.x)
		if platformList[hero.platform].type == 3 then
			if hDistance > 236 then
				hero.platform = false
				hero.jumpState = "falling"
				hero.yVel = -10
			end
		elseif platformList[hero.platform].type == 2 then
			if hDistance > 156 then
				hero.platform = false
				hero.jumpState = "falling"
				hero.yVel = -10
			end	
		end	
	else -- hero is not on a platform
		if (botLeftTile == 1) and (botRightTile == 1) and (hero.jumpState == "grounded") then	
			-- both bottom corners will be on an air tile
			hero.yVel = -10
			hero.jumpState = "falling"	
		elseif (botLeftTile == 8) and (botRightTile == 8) and (hero.jumpState == "grounded") then
			if hero.finished == false then
				--print("finished!")
				audio.stop(1)
				audio.play(victorySound)
					-- write unlocked and score into levelTable data and SAVE!
				-- set current level unlocked from PLAYABLE to PLAYED				
				levelTable.data[levelMap.number][3]=2
				-- set next level unlocked from LOCKED to PLAYABLE
				levelTable.data[levelMap.number+1][3]=1
				-- set scores (temp to Gold Silver Bronze!)
				levelTable.data[levelMap.number][7]=3
				levelTable.data[levelMap.number][8]=2
				levelTable.data[levelMap.number][9]=1
				-- then SAVE the levelTable
				levelTable.save()
				
					hero.finished = true
				UI.closeShutterLevelFinished()
			end
		elseif (botLeftTile == 4) or (botRightTile == 4) and (hero.jumpState == "grounded") then
			-- hero is on death tile with one or two corners
			if hero.dead == false then
				hero.die()
			end
		else 
			-- hero is still standing on solid, cloud, ice or jump with one or both bottom corners
		end
end


[TOPIC: post.html]
#33

thomas6

[GLOBAL: userInfoPane.html]
thomas6
  • Contributor

  • 976 posts
  • Corona SDK

The true essence of the code is that you do the following for each frame (so in an enterFrame loop) for your player character, and possibly other characters:

xSpeed = xSpeed + xAcceleration -- when going left
xSpeed = xSpeed - xAcceleration -- when going right
xSpeed = xSpeed * xFriction -- when slowing doing, with xFriction at .9 or .8

xPositionNext = xPositionCurrent + xSpeed

The same goes for the vertical speed, but it's a bit different because you check for jumping state etc...

 

After you've calculated the next X and Y position, you do a collision check for these "next" positions by checking if any of the four corners of your player are in a solid tile in the "next" position. If none is in a solid tile, your can set your player to this "next" position, and it becomes the current position. If there is a collision you need to correct your next position (and player speed), and then set this corrected next position to the current player position.

 

That's really the whole basic concept!



[TOPIC: post.html]
#34

rakoonic

[GLOBAL: userInfoPane.html]
rakoonic
  • Contributor

  • 112 posts
  • Corona SDK

I spent 15 minutes going over that doc, and once you ignore all the Flash bits, you are left with a very simplistic and occasionally factually incorrect set of sections once things get beyond the basic 'here's rectangles, let's collide with other rectangles'.

Here's the more important obvious errors it contains:

 

- Player rectangle can't be bigger than tiles.

- Platforms only move in a single axis.

- Slopes can only be fully diagonal, other (freeform) shapes aren't possible.

 

There's also other things which don't apply to Corona (I mean apart from the general coding differences), merging clips in the section on ladders, for example, as well as many areas where Corona would be vastly better (you don't need to move every tile when scrolling for example, you just stick everything into a group and move that, using animations to change the frames).

 

Given the emphasis on Flash-based structure, I'd sadly say that for us *at best* the document is useful merely as a theoretical framework, but much of that strength is lost because of the non-native English writer and errors :(

 

The most interesting parts are those less often covered in tile-based games - isometric and pathfinding, but these are of limited value to most people.



[TOPIC: post.html]
#35

rakoonic

[GLOBAL: userInfoPane.html]
rakoonic
  • Contributor

  • 112 posts
  • Corona SDK

Woah I have a lot of catching up to do, the Corona email only sent me to the first new post and I, like an idiot, failed to see there were more as they'd gone to page 2. Forgive me if my post above is a little out of context as a result!



[TOPIC: post.html]
#36

rakoonic

[GLOBAL: userInfoPane.html]
rakoonic
  • Contributor

  • 112 posts
  • Corona SDK

OK it seems to me we are talking about two different approaches to tile-based games.

Thomas6 and jerejigga are covering a basic, beginner-style one (no offense to either of you intended, I'm not referring to your knowledge levels, merely the complexity of the game itself), while Caleb and myself are involved in a more complicated type of engine.

 

Again though, let's not forget we can split a tile-based game into 2 areas - the drawing (which MTE and Dusk both handle), and the collision (I've not seen anything to suggest MTE can handle complicated collisions, but Dusk is definitely moving in the right direction, you just need patience with Caleb!).

 

So for now, for a simple platformer or zelda-type game there's no reason you couldn't use Dusk to draw, and that document to create a simple collision engine (unlike Caleb, I don't like calling these a physics engine, dunno why not!). And it is likely more than good enough for a simple game.

 

I do feel though that gamers today expect a little more functionality and polish than what that document can provide. However, we all start somewhere, and I wouldn't advocate waiting until you have the perfect code to release your first game, so I'd say that the simpler code would actually be ideal for a first couple of games, and I'll try to bear that in mind as this thread develops.

 

However, if Caleb generously releases his code later on with his full physics*, it would almost immediately make many things redundant :)

 

* Or indeed, if I do, but I'm not really sure whether I'll do that, although I might look into releasing protected libraries as plugins or something.

 

Bottom line is, it's all good!



[TOPIC: post.html]
#37

thomas6

[GLOBAL: userInfoPane.html]
thomas6
  • Contributor

  • 976 posts
  • Corona SDK

Hi Rakoonic,

 

No offense taken. My doc is more about a Mario-level of complexity, that's true. However, it's pretty easy to expand things, to be honest. As it stands, the physics implementation is limited, but that's also the beauty of working with it.

 

p.s. Making players bigger than tiles is easy: you just need to check collision on more than the four corner points.



[TOPIC: post.html]
#38

redninjacat

[GLOBAL: userInfoPane.html]
redninjacat
  • Contributor

  • 196 posts
  • Corona SDK

No offense taken here either. And yes, I agree, we are talking about at least two different things. To your last points Rakoonic, that is basically my plan. I need to start somewhere and it makes logical sense to start simple. I will build on that and eventually create something sophisticated and truly amazing. One of things I originally asked for in this forum topic was for a how-to guide. What I am getting instead its lots of theories and concepts. In the long run, that is exactly what I need and far more valuable than a how-to guide! That said, even though the PDF that thomas6 shared might have issues, it's still very thought-provoking. Earlier, I shared a link to a YouTube video series that someone else pointed me to. I have been working through that series and it definitely has flaws. It's still very valuable to me though because it's forcing me to think through the approach. Ideally, I would have a YouTube video series or PDF or web page series that provides a walkthrough of creating a Platformer using Corona SDK, from scratch, using all the best practices! Until that comes into being though, this what I have. :)



[TOPIC: post.html]
#39

thomas6

[GLOBAL: userInfoPane.html]
thomas6
  • Contributor

  • 976 posts
  • Corona SDK

Just a note: for MetroidVania-type platformers my simple physics should be plenty powerful to use as a base.



[TOPIC: post.html]
#40

spideri

[GLOBAL: userInfoPane.html]
spideri
  • Contributor

  • 106 posts
  • Corona SDK

Amazing thread guys. I especially loved your demo Rakoonic, very impressive indeed. Also agree about not liking the event model.

 

If I can be so cheeky, we were thinking of doing some optimisations to our tile maps for when the player zooms out. At that point you can have around 30x30 tiles of 256x256 on screen at the same time. Corona handles it admirably but I don't believe any MIP mapping is being done by Corona. Does anyone have any suggestions of how best to do it? My best guess at the moment is just to have 3 maps identically overlaid, all the same layout, using 3 sets of tiles (256, 128, or 64 squared) and for us to switch between them depending on the zoom. Hopefully we'll get to it in the next month but any other suggestions are very welcome. Thanks!



[TOPIC: post.html]
#41

StarCrunch

[GLOBAL: userInfoPane.html]
StarCrunch
  • Contributor

  • 814 posts
  • Corona SDK

@spideri I figure the ideas in this thread need some renewed attention given the new canvas texture feature. One use case might be to use the latter to build a mipmap and then expand upon Caleb P's technique to actually implement it. (Then again, I suppose this would exacerbate the dearth of inputs.) Are these meant as speed optimizations? Quality? Both?

 

An alternate approach I'm meaning to explore is to paint image sheet fill-based 2x2 rects into a texture to encode the corner texture coordinates, rather than indices, into a canvas, then look those up in-shader and do whatever. (I'm thinking animated cross-frame effects on sprites, for instance, but it might have implications for tile maps, too.) It remains to be seen if this can be done sanely in light of texture filtering.



[TOPIC: post.html]
#42

rakoonic

[GLOBAL: userInfoPane.html]
rakoonic
  • Contributor

  • 112 posts
  • Corona SDK

@spideri - if the only actual difference between maps are the tile-sizes, you could probably get away with just swapping the tileset used at an appropriate distance. I guess it depends entirely on how quick the zoom is as to whether it is noticeable or not.

 

@starcrunch - stop coming up with interesting ideas I want to explore! But realistically, I'm not sure what benefits it would bring. One thing that you need to be careful of with shaders is pixelisation - this may or may not be a good thing depending on what you want, but unless you really want to slow things down in a shader, you are only going to ever have a single discrete pixel lookup which means no sub-pixel rendering.

 

I think for animated tiles I can see where you are going with your idea, but to be honest I'd probably rather just set up some special animation frames. I can't think off-hand how to set this up in Tiled though (but I'm using their tile animation creation which works with discrete tiles just fine, so there *may* be a way to hack this - likely I'd set up the animation as a tile property (IE tile 345 has an anim property that defines how it changes over time, then at level load it uses this info to create the extra frames needed). It would be interesting to be able to paint tiles in differing resolutions all at once though, although frankly again, pixelisation could be a problem there.



[TOPIC: post.html]
#43

spideri

[GLOBAL: userInfoPane.html]
spideri
  • Contributor

  • 106 posts
  • Corona SDK

@StarCrunch - Thanks for the links, we will check them out in more detail when we start doing some tests. We're after quality and speed improvements: quality because when you're zoomed all the way out the bilinear filtering doesn't cut it and we get pixels flickering when it scrolls due to the super high source resolution, and speed because I'm sure it could be a lot faster with lower res textures when zoomed out. Cross-fading between the texture sets could be interesting as you pass the border between them (e.g. low to medium). Kinda like tri-linear filtering... but not. Still, it might help mitigate any visual jumps as it switches between sets.

 

@rakoonic - We run at 60 (where possible) so dropping frames when it swaps would not be ideal.

 

Thanks again guys, I think we just need to do some tests.



[TOPIC: post.html]
#44

rakoonic

[GLOBAL: userInfoPane.html]
rakoonic
  • Contributor

  • 112 posts
  • Corona SDK

I should point out that many tile-engine-based games will fall foul of the horrible gap visual problem (or, if you've set up the tile image differently without gaps between tiles, tile-bleed). I've been attempting to use the new canvas texture feature to get around this, and while there's still a few issues (an inexplicable delay in corona updating graphics which I'll hopefully be able to prod Vlad into looking at once I've simplified my test case, some fighting with texture mag/min filters, and overriding content scaling without actually checking if  the files exist) it is working.

What this means is you don't need to manually expand the borders of each tile graphics, which is a tedious and soul-crushing task, as it can be done automatically.

 

If you look here: https://www.dropbox.com/s/ir48kkh7an1si2u/RagnarRock.apk?dl=0

 

What you'll see is some pixelation (that's the mag/min problem from above), but what you won't see are gaps between the tiles, despite the base 2x image normally showing them (controls aren't 100%, this is just my platformer testbed - down in front of an open door to go through).



[TOPIC: post.html]
#45

redninjacat

[GLOBAL: userInfoPane.html]
redninjacat
  • Contributor

  • 196 posts
  • Corona SDK

@rakoonic I have seen the gap issue in both MTE and Dusk! I was hoping that was just a Windows simulator issue! Are you saying that I should expect that to happen on devices too? How in the world could I create a Tiled based game if I can expect this horrible issue to occur?



[TOPIC: post.html]
#46

thomas6

[GLOBAL: userInfoPane.html]
thomas6
  • Contributor

  • 976 posts
  • Corona SDK

Hi all,

 

I've had fairly big success sometimes by just scaling my tiles to 101 or 102 percent. Do check on different devices however, because sometimes one devices (e.g. iPhone) works best at one setting and another (e.g. iPad) works best at another setting - potentially fixed with clever device detection.

 

Not a magic fix mind you, but works well for some projects.



[TOPIC: post.html]
#47

rakoonic

[GLOBAL: userInfoPane.html]
rakoonic
  • Contributor

  • 112 posts
  • Corona SDK

Yup, that's the simplest solution, and I won't knock it for its simplicity, but I'm after a more solid, pixel-perfect solution. If only I'd known the torture it would bring me while searching!

 

@jerejigga - there is a way to avoid it (I mean, apart from thomas6's suggestion above).

Although the problem happens slightly when shrunk, this is most noticable when you zoom a tile image using "linear" filtering, so I'll concentrate on explaining why that happens.

Linear filtering uses a nice blend between pixel colours. Say you have 2 pixels in the source image next to each other. White and black. Now, when you show that image zoomed, you'll actually see a smooth fade from white to black. The GPU is linearly blending between pixels, so they don't look all pixelated and silly up close (unless that is what you want!).

Now, with that in mind, what happens when you try to draw the edge of a tile sprite? Well, it depends on how you set up your source tile set image.

If each source tile touches the tiles next to it, when you try to draw your view, each sprite will have a border where the tile you want's edge pixels blend into those of the tile next to it. Often not good.

The next possibility (and what I recommend) is that individual tile graphics don't touch either other in the source tile set image. You leave ideally a 2 pixel or greater gap between them all, both horizontally and vertically. However, this now means that when you draw a tile sprite, the edge pixels are now blending from the actual graphic, into pure transparent. This is where the gaps come from.

 

So, the proper solution (and this is a known thing when working on this type of game), is to do your tile set graphics, then expand the edge pixels of each tile graphic by 1 - IE for each tile you duplicate its left edge one pixel to its left, its right edge one pixel to its right, and the same for up and down etc. This means edge pixels will be blended with a copy of themselves, so they'll look 99% correct - hurrah!

 

However, this is a pain if you are still editing the tile set graphics. It is a tedious and frustrating task at the best of times. If you don't do it though, your graphics in-game while testing look rubbish.

 

So, what I've been struggling to do, and with some degree of success, is using the new canvas object to automatically expand the borders.

 

Essentially what I do is load the tile set graphic into a canvas. Then I make it an image sheet, making sprite frames from the edge pixels of each tile that I need to expand. Then I :draw() a lot of sprites into the self-same canvas using these frames to offset the tile edges, finishing with an :invalidate(). Then I create another imagesheet from the canvas, which is the normal one I'd use for my tile sets as if I'd done none of this expansion stuff.

 

So what are my problems? Firstly you *must* do all this edge expansion stuff with the texture min and mag filters set to "nearest" or it just goes wrong. That alone isn't bad, but it means that any use of the tile set canvas later on will use these filters, which is likely *not* what you want to happen.

Secondly, because you actually want the corner pixels expanded, I'm doing my edge expansion in 2 sets. Firstly horizontal, then vertical. The vertical ones will use the already horizontally expanded source, so they now have these corner things. The problem is that for some reason I can't just update and invalidate() the canvas horizontally 1 frame after initialising it, then doing the same for vertical adjustments in the following frame after that. It *should* work, and indeed in the simulator it does. But on my device, I need to leave a delay of roughly 700 milliseconds between updates. This isn't a huge deal, but it means something is going on I'm not aware of, and means you can't start showing the level until approximately 1.5 seconds after you set it up. Not something I'd want in a publically released library :/



[TOPIC: post.html]
#48

thomas6

[GLOBAL: userInfoPane.html]
thomas6
  • Contributor

  • 976 posts
  • Corona SDK

Hi Rakoonic,

 

Very good points. Might I suggest not to forget that you can create a Photoshop action to do the necessary duplicating of the edges - and I even believe that TexturePacker has an option for this, but I'm not sure.



[TOPIC: post.html]
#49

redninjacat

[GLOBAL: userInfoPane.html]
redninjacat
  • Contributor

  • 196 posts
  • Corona SDK

@thomas6 You are correct about TexturePacker. It does have an option called "extrude" that behaves as you describe.

 

@thomas6 and @rakoonic I am glad to hear that there are workarounds for the gap issue! Presently I have been playing with tilesets created by other people so it seems I am stuck using the 101-102% zoom as suggested by thomas6. Rakoonic, once you get your solution working properly, I hope that you will be sharing it. :)



[TOPIC: post.html]
#50

rakoonic

[GLOBAL: userInfoPane.html]
rakoonic
  • Contributor

  • 112 posts
  • Corona SDK

I spent several days on trying to programatically fix the gaps between tiles, instead of requiring photoshop action etc.

I failed, but I did discover some interesting bugs which will be passed to the relevant people :)

 

On the other hand, the splendiferous Vlad from Corona did pass me this gem which has flown completely under the radar until now:

 

display.setDefault( "isImageSheetSampledInsideFrame", true )
 
 
This will likely fix most of your tile gap problems :D Set it before you load any tile images and / or make sprite sheets from tiles (and don't forget to reset it afterwards).
Now, it does still have some issues - in particular if you are running your app on a much higher res screen than you generally cater for, or if you zoom the tiles significantly, but those are specific cases (and in the case of high res screens, you should be running x2 or x3 assets to cater for it. For zoomed up pixel art, you'll still need to manually extrude edges).
But, it helps a ton generally.
Try it, love it, thank Vlad!
 
Here's a couple of links so you can see if it works for you or not:
 
OSX Build (no onscreen controls, use cursor keys to move): https://www.dropbox.com/s/jl2ynkuq43r61nm/RagnarRock.zip?dl=0

PS, let me know if it runs at 60fps nicely, and what you think of the slightly semi-trans near layer of parallax (I made it semi-trans because I got annoyed at it blocking the player entirely).



[topic_controls]
Page 2 of 2 1 2
 
[/topic_controls]