• Register

Gideros provides the cross-platform technology to create amazing games. In a couple of hours, you’ll find yourself building and running your next great game.

Post tutorial Report RSS Clipping in Gideros with renderTarget

This is a tutorial about how to use renderTarget in Gideros, especially to achieve clipping. Gideros is a cross-platform mobile lua rapid game-development framework (currently for android/ios). This tutorial is assuming a beginner level knowledge of Gideros, although if you are familiar with any programming language/framework, you should be able to understand most of the codes and the tutorial.

Posted by on - Intermediate Client Side Coding

Tutorial: Clipping in Gideros with renderTarget

This is a tutorial about how to use renderTarget in Gideros, especially to achieve clipping. Gideros is a cross-platform mobile lua rapid game-development framework (currently for android/ios). To start to develop your games in Gideros for free, go to Giderosmobile.com . This tutorial is assuming a beginner level knowledge of Gideros, although if you are familiar with any programming language/framework, you should be able to understand most of the codes and the tutorial.

Remark: since the original publication of this tutorial, real clipping capability was added to Gideros, thus now it's easy to clip using the Sprite:setClip function, see Docs.giderosmobile.com for details. Nevertheless this tutorial remains to be a good introduction on how to use renderTarget.

Introduction

In Gideros you can do many things very simply and fast, yet clipping is an important feature that is not yet implemented in a straightforward way. On the other hand with the renderTarget feature you can actually do any kind of clipping. As it's not evident how, I decided to make this tutorial to explain the details. This tutorial should help you to use renderTarget in many other ways as well, not just simple clipping. I think reading this tutorial is also a good way to understand better some other features of Gideros (object oriented programming, sprite hierarchy, etc.). This tutorial was made during the Sharebruary program of Gideros offering free limited licence for people creating tutorials during February of 2014, more info here: Blog.giderosmobile.com .

The set-up

For simplicity we modify an example that comes with Gideros, the Drag Me example. You should open it or start a new project with the same properties (320x480 logical dimensions etc.) and copy the code below. From now on we assume that you are familiar with this example and know how it works, so experiment a bit with it if you need. In a nutshell, it puts 5 rectangular shapes in random positions and attaches mouse event handlers to each of them so that they are draggable.

--[[
Drag the shapes around with your mouse or fingers
This code is MIT licensed, see http://www.opensource.org/licenses/mit-license.php
(C) 2010 - 2011 Gideros Mobile
]]

local function onMouseDown(self, event)
	if self:hitTestPoint(event.x, event.y) then
		self.isFocus = true
		self.x0 = event.x
		self.y0 = event.y

		event:stopPropagation()
	end
end

local function onMouseMove(self, event)
	if self.isFocus then
		local dx = event.x - self.x0
		local dy = event.y - self.y0
		self:setX(self:getX() + dx)
		self:setY(self:getY() + dy)
		self.x0 = event.x
		self.y0 = event.y

		event:stopPropagation()
	end
end

local function onMouseUp(self, event)
	if self.isFocus then
		self.isFocus = false
		
		event:stopPropagation()
	end
end

for i=1,5 do
	local shape = Shape.new()
	shape:setLineStyle(3, 0x000000)
	shape:setFillStyle(Shape.SOLID, 0xff0000, 0.5)
	shape:beginPath()
	shape:moveTo(0, 0)
	shape:lineTo(100, 0)
	shape:lineTo(100, 50)
	shape:lineTo(0, 50)
	shape:closePath()
	shape:endPath()

	shape:setX(math.random(0, 320 - 100))
	shape:setY(math.random(0, 480 - 50))

	shape.isFocus = false

	shape:addEventListener(Event.MOUSE_DOWN, onMouseDown, shape)
	shape:addEventListener(Event.MOUSE_MOVE, onMouseMove, shape)
	shape:addEventListener(Event.MOUSE_UP, onMouseUp, shape)

	stage:addChild(shape)
end

local info = TextField.new(nil, "drag the shapes around with your mouse or fingers")
info:setPosition(23, 50)
stage:addChild(info)

Gideros Clipping Tutorial Fig  1

Clipping Part I: Clipping

In this step we change the code in the following ways. See Docs.giderosmobile.com for reference about the usage of renderTarget.

First we add the following code right before the for cycle:

toRender=Sprite.new()
renderTarget=RenderTarget.new(320, 240, true)
render=Bitmap.new(renderTarget)
stage:addChild(render)

function updateRender()
	renderTarget:clear(0,0)
	renderTarget:draw(toRender)
end

Explanation:

Line 1: toRender is a dummy Sprite, all the sprites that we want to clip will be added as a child to this Sprite instead of the stage. Notice that this dummy is not even added to the stage.

Line 2: renderTarget is a new RenderTarget object, which is in fact a texture, that can be used as a texture to make a bitmap from it or even arbitrary shapes could use it, this way we can achieve complicated clippings very easily, we don't need to restrict ourselves to rectangular clippings. Its size is half of the screen (320x240) and the last parameter true allows antialiasing of this texture.

Line 3: render is a Bitmap generated with the texture renderTarget.

Line 4: we add render to the stage. This way whatever is drawn on renderTarget, it is applied as a texture to render and at the end it is put on screen as render is added to stage.

Function updateRender: when this function is called, the renderTarget texture is cleared first with color 0 and alpha 0, so it is completely empty, then toRender is drawn to renderTarget, which draws every child (and grandchild, etc.) of the dummy object toRender on renderTarget.

In the for cycle we change one line as well to add the rectangles to our dummy sprite toRender instead of stage:

stage:addChild(shape)

is changed to

toRender:addChild(shape) 

Finally we call our new function right after the for cycle:

updateRender()

Gideros Clipping Tutorial Fig  2

As you can see, the rectangles are clipped indeed to the upper half of the screen. Yet, now you cannot interact with them. This is because events are received by an object only if that object is added to the stage, and now our rectangles are not added to the stage, only to the dummy object toRender. We will solve this problem in Part II of our Tutorial. The full code after completing Part I should look like this:

local function onMouseDown(self, event)
	if self:hitTestPoint(event.x, event.y) then
		self.isFocus = true
		self.x0 = event.x
		self.y0 = event.y

		event:stopPropagation()
	end
end

local function onMouseMove(self, event)
	if self.isFocus then
		local dx = event.x - self.x0
		local dy = event.y - self.y0
		self:setX(self:getX() + dx)
		self:setY(self:getY() + dy)

		self.x0 = event.x
		self.y0 = event.y
		
		event:stopPropagation()
	end
end

local function onMouseUp(self, event)
	if self.isFocus then
		self.isFocus = false

		event:stopPropagation()
	end
end

function updateRender()
	renderTarget:clear(0,0)
	renderTarget:draw(toRender)
end

toRender=Sprite.new()
renderTarget=RenderTarget.new(320, 240, true)
render=Bitmap.new(renderTarget)
stage:addChild(render)

for i=1,5 do
	local shape = Shape.new()

	shape:setLineStyle(3, 0x000000)
	shape:setFillStyle(Shape.SOLID, 0xff0000, 0.5)
	shape:beginPath()
	shape:moveTo(0, 0)
	shape:lineTo(100, 0)
	shape:lineTo(100, 50)
	shape:lineTo(0, 50)
	shape:closePath()
	shape:endPath()

	shape:setX(math.random(0, 320 - 100))
	shape:setY(math.random(0, 480 - 50))

	shape.isFocus = false

	shape:addEventListener(Event.MOUSE_DOWN, onMouseDown, shape)
	shape:addEventListener(Event.MOUSE_MOVE, onMouseMove, shape)
	shape:addEventListener(Event.MOUSE_UP, onMouseUp, shape)

	toRender:addChild(shape)
end

updateRender()

local info = TextField.new(nil, "drag the shapes around with your mouse or fingers")
info:setPosition(23, 50)
stage:addChild(info)

Clipping Part II: Interaction

Now we need to do some trick so that our rectangles receive mouse events yet they remain invisible. If a sprite is added to the stage hierarchy but has been applied setVisible(false) then it receives events yet it is not visible, just as we want. So we will do this, while maintaining that even though the are invisible, they are drawn onto the renderTarget texture.

Again, before the for cycle we add the following code:

clipObjects=Sprite.new()
clipObjects:addChild(toRender)
stage:addChild(clipObjects)
clipObjects:setVisible(false) 

Explanation:

Line 1: clipObjects is another dummy Sprite.

Line 2: recall that all the sprites that we want to clip were added as a child to toRender. Now toRender is added as a child to clipObjects.

Line 3: clipObjects is added to stage. This way all clippable objects are in the stage render hierarchy (see also Line 2), thus they are receiving mouse events.

Line 4: clipObjects:setVisible(false) makes sure that everything that is under the clipObjects hierarchy is invisible (but still recieves the mouse etc. Events).

Note that although all rectangles will be non-visible by Line 4, still they are drawn as visible to renderTarget because we draw toRender on renderTarget and locally in the hierarchy at and below toRender, everything is visible. This is the trick we needed.

So far our (invisible) rectangles became draggable, to make their movement visible, we need to add the following line at the end of each event handler function, so that our renderTarget always gets updated:

updateRender()

Our job is almost complete, however, when you start to play with the example you may notice that our rectangles are draggable even when out of the clipping area. In most cases we don't want that so in the event handlers we also check if we are in the clipping area, that is above the render object. To achieve this, simply change in the function onMouseDown the first line from

if self:hitTestPoint(event.x, event.y) then 

to

if self:hitTestPoint(event.x, event.y) and render:hitTestPoint(event.x, event.y) then

. The whole code of the final program is as follows:

local function onMouseDown(self, event)
	if self:hitTestPoint(event.x, event.y) and render:hitTestPoint(event.x, event.y) then
		self.isFocus = true
		self.x0 = event.x
		self.y0 = event.y

		event:stopPropagation()
		
		updateRender()
	end
end

local function onMouseMove(self, event)
	if self.isFocus then
		local dx = event.x - self.x0
		local dy = event.y - self.y0
		self:setX(self:getX() + dx)
		self:setY(self:getY() + dy)
		self.x0 = event.x
		self.y0 = event.y
		
		event:stopPropagation()

		updateRender()
	end
end

local function onMouseUp(self, event)
	if self.isFocus then
		self.isFocus = false
		
		event:stopPropagation()
		
		updateRender()
	end
end

function updateRender()
	renderTarget:clear(0,0)
	renderTarget:draw(toRender)
end

toRender=Sprite.new()
renderTarget=RenderTarget.new(320, 240, true)
render=Bitmap.new(renderTarget)
stage:addChild(render)

clipObjects=Sprite.new()
clipObjects:addChild(toRender)
clipObjects:setVisible(false)
stage:addChild(clipObjects)

for i=1,5 do
	local shape = Shape.new()
	shape:setLineStyle(3, 0x000000)
	shape:setFillStyle(Shape.SOLID, 0xff0000, 0.5)
	shape:beginPath()
	shape:moveTo(0, 0)
	shape:lineTo(100, 0)
	shape:lineTo(100, 50)
	shape:lineTo(0, 50)
	shape:closePath()
	shape:endPath()

	shape:setX(math.random(0, 320 - 100))
	shape:setY(math.random(0, 240 - 50))

	shape.isFocus = false

	shape:addEventListener(Event.MOUSE_DOWN, onMouseDown, shape)
	shape:addEventListener(Event.MOUSE_MOVE, onMouseMove, shape)
	shape:addEventListener(Event.MOUSE_UP, onMouseUp, shape)
	
	toRender:addChild(shape)
end

updateRender()

local info = TextField.new(nil, "drag the shapes around with your mouse or fingers")
info:setPosition(23, 50)
stage:addChild(info)

Clipping Part III: Clip a different region

Now we make some changes so that the lower half of the screen is clipped. For this we make the renderTarget to cover the full screen, but instead of using this as a texture, we use only the lower half of it, which is cut out into a texture renderTargetClipped using TextureRegion. At the end we have to reposition render so that it is in the correct place.

To do all this, we change these lines:

toRender=Sprite.new()
renderTarget=RenderTarget.new(320, 240, true)
render=Bitmap.new(renderTarget)
stage:addChild(render)

to these:

toRender=Sprite.new()
renderTarget=RenderTarget.new(320, 480, true)
renderTargetClipped=TextureRegion.new(renderTarget, 0, 240, 320, 240)
render=Bitmap.new(renderTargetClipped)
stage:addChild(render)
render:setY(240) 

Gideros Clipping Tutorial Fig  3

Clipping Part IV: Create a Clip Class to easily handle multiple clips

Finally, we can easily wrap these into a class and then we can easily create a clip both for the lower and upper half of the screen. If you are not yet familiar with classes etc. in lua and Gideros, then note that self.render is NOT a child of self, the '.' is used to define a variable only (which can be anything in lua). In this case self.render is the render object (which was called simply render before) corresponding to self (which was 'toRender' before and inside Clip:init it refers to the Clip object).

The only thing we have to be careful about is that when in the onMouseDown function we checked

render:hitTestPoint(event.x, event.y)

we have to test always the appropriate render object, this is done by changing the above line to:

self:getParent().render:hitTestPoint(event.x, event.y)

. The complete code is the following.

Clip = gideros.class(Sprite)

function Clip:init(startX,startY,sizeX,sizeY)
	self.renderTarget=RenderTarget.new(320, 480, true)
	self.renderTargetClipped=TextureRegion.new(self.renderTarget, startX,startY,sizeX,sizeY)
	self.render=Bitmap.new(self.renderTargetClipped)
	stage:addChild(self.render)
	self.render:setPosition(startX,startY)

	self.clipObjects=Sprite.new()
	self.clipObjects:addChild(self)
	self.clipObjects:setVisible(false)
	stage:addChild(self.clipObjects)
end

function Clip:updateRender()
	self.renderTarget:clear(0,0)
	self.renderTarget:draw(self)
end

local function onMouseDown(self, event)
	if self:hitTestPoint(event.x, event.y) and self:getParent().render:hitTestPoint(event.x, event.y) then
		self.isFocus = true
		self.x0 = event.x
		self.y0 = event.y

		event:stopPropagation()

		updateRender()
	end
end

local function onMouseMove(self, event)
	if self.isFocus then
		local dx = event.x - self.x0
		local dy = event.y - self.y0
		self:setX(self:getX() + dx)
		self:setY(self:getY() + dy)
		self.x0 = event.x
		self.y0 = event.y
		
		event:stopPropagation()

		updateRender()
	end
end

local function onMouseUp(self, event)
	if self.isFocus then
		self.isFocus = false

		event:stopPropagation()

		updateRender()
	end
end

function updateRender()
	clipTop:updateRender()
	clipBottom:updateRender()
end

clipTop=Clip.new(0,0,320,240)
clipBottom=Clip.new(0,240,320,240)

for i=1,5 do
	local shape = Shape.new()
	shape:setLineStyle(3, 0x000000)
	shape:setFillStyle(Shape.SOLID, 0xff0000, 0.5)
	shape:beginPath()
	shape:moveTo(0, 0)
	shape:lineTo(100, 0)
	shape:lineTo(100, 50)
	shape:lineTo(0, 50)
	shape:closePath()
	shape:endPath()

	shape:setX(math.random(0, 320 - 100))
	shape:setY(math.random(0, 240 - 50))

	shape.isFocus = false

	shape:addEventListener(Event.MOUSE_DOWN, onMouseDown, shape)
	shape:addEventListener(Event.MOUSE_MOVE, onMouseMove, shape)
	shape:addEventListener(Event.MOUSE_UP, onMouseUp, shape)

	clipTop:addChild(shape)
end

for i=1,5 do
	local shape = Shape.new()
	shape:setLineStyle(3, 0x000000)
	shape:setFillStyle(Shape.SOLID, 0xff0000, 0.5)
	shape:beginPath()
	shape:moveTo(0, 0)
	shape:lineTo(100, 0)
	shape:lineTo(100, 50)
	shape:lineTo(0, 50)
	shape:closePath()
	shape:endPath()

	shape:setX(math.random(0, 320 - 100))
	shape:setY(math.random(0, 240 - 50)+240)

	shape.isFocus = false

	shape:addEventListener(Event.MOUSE_DOWN, onMouseDown, shape)
	shape:addEventListener(Event.MOUSE_MOVE, onMouseMove, shape)
	shape:addEventListener(Event.MOUSE_UP, onMouseUp, shape)

	clipBottom:addChild(shape)
end

updateRender()

local info = TextField.new(nil, "drag the shapes around with your mouse or fingers")
info:setPosition(23, 50)
stage:addChild(info)

Gideros Clipping Tutorial Fig  4

Conclusion

Using the last code you can easily make your own clippings, in fact you can do this with one line even if you did not understand completely how it works, something like this:

clip=Clip.new(40,40,80,80)

Remember that the objects you want to be clipped has to be added to this clip as a child, yet clip should not be added to the stage as a child. Instead, clip.render is added to the stage automatically when clip is initialized (so you don't need to add on your own). You can also easily change this code so that it handles more complicated areas, then instead of self.render being a Bitmap, it has to be a Shape which has self.renderTarget or self.renderTargetClipped as a texture. Also, don't forget to update clip by calling clip:updateRender() whenever necessary, this may be needed on all onEnterFrame events, all mouse events, etc.

Post a comment

Your comment will be anonymous unless you join the community. Or sign in with your social account: