CBump Template

From GiderosMobile
Revision as of 17:25, 10 December 2019 by MoKaLux (talk | contribs) (MoKaLux moved page CBump to CBump Template: not to interfere with cbump function)
The printable version is no longer supported and may have rendering errors. Please update your browser bookmarks and please use the default browser print function instead.

Here you will find various resources to help you create games and apps in Gideros Studio.
note: You may have to provide your own assets (fonts, gfx, …).

Description

This section deals with various ways to use CBump.

Using CBump and TILED (handles collisions)

It's easy: you draw your map with Tile then you export your map to a lua file.
The class handles tile collisions automatically.

-- usage
--local map = CBumpTiled.new("tiled/test.lua", world, bg, true)
-- where world is the cbump world
-- bg is a sprite layer (here a background sprite layer)
-- true if only foreground is added to cbump collision world

CBumpTiled = Core.class(Sprite)

local function gid2tileset(map, gid)
	for i = 1, #map.tilesets do
		local tileset = map.tilesets[i]
		if tileset.firstgid <= gid and gid <= tileset.lastgid then
			return tileset
		end
	end
end

function CBumpTiled:init(filename, xworld, xlayer, xtop)
	-- Bits on the far end of the 32-bit global tile ID are used for tile flags (flip, rotate)
	local FLIPPED_HORIZONTALLY_FLAG = 0x80000000;
	local FLIPPED_VERTICALLY_FLAG   = 0x40000000;
	local FLIPPED_DIAGONALLY_FLAG   = 0x20000000;
	local map = loadfile(filename)()
	for i = 1, #map.tilesets do
		local tileset = map.tilesets[i]
		tileset.sizex = math.floor((tileset.imagewidth - tileset.margin + tileset.spacing) / (tileset.tilewidth + tileset.spacing))
		tileset.sizey = math.floor((tileset.imageheight - tileset.margin + tileset.spacing) / (tileset.tileheight + tileset.spacing))
		tileset.lastgid = tileset.firstgid + (tileset.sizex * tileset.sizey) - 1
		tileset.texture = Texture.new(tileset.image, false, {transparentColor = tonumber(tileset.transparentcolor)})
	end
	for i = 1, #map.layers do
		if map.layers[i].type == "tilelayer" then
			local layer = map.layers[i]
			local tilemaps = {}
			for y = 1, layer.height do
				for x = 1, layer.width do
					local i = x + (y - 1) * layer.width
					local gid = layer.data[i]
					if gid ~= 0 then
						-- Read flipping flags
						flipHor = gid & FLIPPED_HORIZONTALLY_FLAG
						flipVer = gid & FLIPPED_VERTICALLY_FLAG
						flipDia = gid & FLIPPED_DIAGONALLY_FLAG
						-- Convert flags to gideros style
						if(flipHor ~= 0) then flipHor = TileMap.FLIP_HORIZONTAL end
						if(flipVer ~= 0) then flipVer = TileMap.FLIP_VERTICAL end
						if(flipDia ~= 0) then flipDia = TileMap.FLIP_DIAGONAL end
						-- Clear the flags from gid so other information is healthy
						gid = gid & ~ (
							FLIPPED_HORIZONTALLY_FLAG |
							FLIPPED_VERTICALLY_FLAG |
							FLIPPED_DIAGONALLY_FLAG
						)
					end

					local tileset = gid2tileset(map, gid)
					if tileset then
						local tilemap = nil
						tilemap = TileMap.new(
							layer.width, 
							layer.height,
							tileset.texture,
							tileset.tilewidth,
							tileset.tileheight,
							tileset.spacing,
							tileset.spacing,
							tileset.margin,
							tileset.margin,
							map.tilewidth,
							map.tileheight
						)
						tilemaps[tileset] = tilemap
						local tx = (gid - tileset.firstgid) % tileset.sizex + 1
						local ty = math.floor((gid - tileset.firstgid) / tileset.sizex) + 1
						-- Set the tile with flip info
						tilemap:setTile(x, y, tx, ty, (flipHor | flipVer | flipDia))
						xlayer:addChild(tilemap)
						xlayer:setAlpha(layer.opacity)
						if xtop then
							if layer.id == 1 then
								xworld:add(
									tilemap,
									(x - 1) * tileset.tilewidth * xlayer:getScaleX(),
									(y - 1) * tileset.tileheight * xlayer:getScaleY(),
									tileset.tilewidth * xlayer:getScaleX(),
									tileset.tileheight * xlayer:getScaleY()
								)
							end
						else
							xworld:add(
								tilemap,
								(x - 1) * tileset.tilewidth * xlayer:getScaleX(),
								(y - 1) * tileset.tileheight * xlayer:getScaleY(),
								tileset.tilewidth * xlayer:getScaleX(),
								tileset.tileheight * xlayer:getScaleY()
							)
						end
					end
				end
			end
		end
	end
end

Usage

This is an example of how to set up CBump and TILED.

-- cbump
local cbump = require "cbump"
local world = cbump.newWorld() -- default is 64

-- camera
local camera = Sprite.new()
local bg = Sprite.new()
local fg = Sprite.new()
camera:addChild(bg)
camera:addChild(fg)
stage:addChild(camera)

-- tiled
local map = CBumpTiled.new("tiled/test.lua", world, bg, true)

A Full CBump Platformer Level with enemies, collectibles, exit,...

This class is an example of a full CBump platformer level using TILED as a map editor. Please note that you need to modify the code depending on your assets path. Enjoy!

LevelX = Core.class(Sprite)

function LevelX:init()
	-- BG
	application:setBackgroundColor(0x403C38)

	-- cbump
	local cbump = require "cbump"
	self.world = cbump.newWorld() -- default is 64*64

	-- camera
	self.camera = Sprite.new()
	self.bg = Sprite.new()
	self.fg = Sprite.new()
	self.camera:setScale(1.75)
	self.camera:addChild(self.bg)
	self.camera:addChild(self.fg)
	self:addChild(self.camera)

	-- class lists
	self.enemies = {}
	self.coins = {}
	self.bullets = {}

	-- TILED: the level map
	CBumpTiled.new("tiled/level01.lua", self.world, self.bg, true)
	
	-- TILED: process the tiled layers
	local level = loadfile("tiled/level01.lua")()
	local layers = level.layers
	for i = 1, #layers do
		local layer = layers[i]
		local layerType = layer.type
		local layerName = layer.name

		if layerType == "imagelayer" then
			-- process image layer here

		elseif layerType == "objectgroup" then
			local objects = layer.objects
			for i = 1, #objects do
				local object = objects[i]
				local objectName = object.name -- type of object

				if objectName == "player" then
					-- process player start location
					self.player1 = Sprite_Maker.new("player1", "gfx/player/HQ_Trooper_all.png", 6, 13, object.x, object.y)
					self.player1:setScale(0.9)
					self.player1:setAlpha(0.75)
					self.player1.lives = 3
					self.player1.filter = function(item, other)
						if other.name == "coin01" then
							return "cross"
						elseif other.name == "bullet01" then
							return "cross"
						elseif other.name == "exit" then
							return "cross"
						else
							return "slide"
						end
					end
					self.fg:addChild(self.player1)
					self.world:add(self.player1, self.player1.x, self.player1.y, self.player1.w * self.player1:getScaleX(), self.player1.h * self.player1:getScaleY())

				elseif objectName == "e01" then
					local enemy = Sprite_Maker.new("enemy01", "gfx/nmes/snake.png", 5, 4, object.x, object.y)
					enemy:setScale(0.85)
					enemy.lives = math.random(2, 4)
					enemy.vx = 32
					enemy.filter = function(item, other)
						if other.name == "enemy01" or other.name == "enemy02" then
							--return "cross"
						elseif other.name == "exit" then
							--return "cross"
						else
							return "slide"
						end
					end
					self.enemies[#self.enemies + 1] = enemy
					self.bg:addChild(enemy)
					self.world:add(enemy, enemy.x, enemy.y, enemy.w * enemy:getScaleX(), enemy.h * enemy:getScaleY())

				elseif objectName == "e02" then
					local enemy = Sprite_Maker.new("enemy02", "gfx/nmes/enemy01.png", 23, 4, object.x, object.y)
					enemy:setScale(1.25)
					enemy.lives = math.random(3, 5)
					enemy.vx = 18
					enemy.filter = function(item, other)
						if other.name == "enemy01" or other.name == "enemy02" then
							--return "cross"
						elseif other.name == "exit" then
							--return "cross"
						else
							return "slide"
						end
					end
					self.enemies[#self.enemies + 1] = enemy
					self.bg:addChild(enemy)
					self.world:add(enemy, enemy.x, enemy.y, enemy.w * enemy:getScaleX(), enemy.h * enemy:getScaleY())

				elseif objectName == "c01" then
					local coin01 = Sprite_Maker.new("coin01", "gfx/coins/coin_20_x01.png", 6, 1, object.x, object.y + 48)
					coin01:setAnchorPoint(0.5, 0.5)
					self.coins[#self.coins + 1] = coin01
					self.bg:addChild(coin01)
					self.world:add(coin01, coin01.x, coin01.y, coin01.w * coin01:getScaleX(), coin01.h * coin01:getScaleY())

				elseif objectName == "exit" then
					local texture = Texture.new("gfx/doors/door.png")
					local tile = Bitmap.new(texture)
					tile.name = "exit"
					tile:setPosition(object.x, object.y)
					self.bg:addChild(tile)
					self.world:add(tile, object.x, object.y, tile:getWidth(), tile:getHeight())

				else
					print("unknown object type")
				end
			end

		else
			print("unknown layer type")
		end
	end

	-- AUDIO
	self.bulletsnd = Sound.new("audio/shoot.wav")
	self.coinsnd = Sound.new("audio/coin.wav")

	-- LISTENERS
	self:addEventListener("enterBegin", self.onTransitionInBegin, self)
	self:addEventListener("enterEnd", self.onTransitionInEnd, self)
	self:addEventListener("exitBegin", self.onTransitionOutBegin, self)
	self:addEventListener("exitEnd", self.onTransitionOutEnd, self)
end

-- GAME LOOP
function LevelX:onEnterFrame(e)
	-- camera follows player
	local playerposx, playerposy = self.player1:getPosition()
	playerposx, playerposy = playerposx * self.camera:getScale(), playerposy * self.camera:getScale()
	local offsetX = -(playerposx - application:getContentWidth() / 2 + self.player1:getWidth() / 4 * self.player1:getScaleX())
	local offsetY = -(playerposy - (4.5 * 64) * self.camera:getScale() / self.player1:getScale())
	self.camera:setPosition(offsetX, offsetY)

	-- player1
	if self.player1.lives > 0 then
		self:updatePlayer1(self.player1, e.deltaTime)
	else
		print("you lose!")
--		scenemanager:changeScene("gameover", 3, transitions[6], easing.outBack)
	end
	-- bullets
	for b = #self.bullets, 1, -1 do
		self:updateBullets(self.bullets[b], e.deltaTime)
	end
	-- bullets hit
	for b = #self.bullets, 1, -1 do
		local sprite = self.bullets[b]
		if sprite.lives <= 0 then
			self.camera:removeChild(sprite)
			self.world:remove(sprite)
			table.remove(self.bullets, b)
		end
	end
	-- enemies
	for n = #self.enemies, 1, -1 do
		self:updateEnemies(self.enemies[n], e.deltaTime)
	end
	-- enemy is dead
	for n = #self.enemies, 1, -1 do
		local sprite = self.enemies[n]
		if sprite.lives <= 0 then
			self.bg:removeChild(sprite)
			self.world:remove(sprite)
			table.remove(self.enemies, n)
		end
	end
	-- coins
	for c = #self.coins, 1, -1 do
		self:updateCollectibles(self.coins[c], e.deltaTime)
	end
	-- collectibles collected
	for c = #self.coins, 1, -1 do
		local sprite = self.coins[c]
		if sprite.lives <= 0 then
			self.bg:removeChild(sprite)
			self.world:remove(sprite)
			table.remove(self.coins, c)
		end
	end
end

-- FUNCTIONS
function LevelX:updatePlayer1(xsprite, dt)
	-- player1 pushed by gravity
	xsprite.vy += GRAVITY * dt

	-- cbump
	local goalx = xsprite.x + xsprite.vx * dt
	local goaly = xsprite.y + xsprite.vy * dt
	local nextx, nexty, collisions, len = self.world:move(xsprite, goalx, goaly, xsprite.filter)
	-- collisions
	for i = 1, len do
		local col = collisions[i]
		-- collectibles collisions
		if col.other.name == "coin01" then
			local sound = self.coinsnd:play()
			sound:setVolume(0.1)
			col.other.lives -= 1
		end
		-- enemy collision
		if col.other.name == "enemy01" or col.other.name == "enemy02" then
			if col.normal.y == -1 then
				col.other.lives -= 1
				col.item.isattacking = true
			end
		end
		-- sprite collides top or bottom
		if col.normal.y == 1 or col.normal.y == -1 then
			if col.other.name == "coin01" or col.other.name == "exit" then
				-- nothing here
			else
				col.item.vy = 0
			end
		end
		-- sprite is on floor
		if col.normal.y == -1 then
			if col.other.name == "coin01" then
				col.item.isonfloor = false
			elseif col.item.isattacking then
				col.item.vy = -col.item.jumpspeed / 1.5
				col.item.isonfloor = false
				col.item.isattacking = false
			else
				col.item.isonfloor = true
			end
		end
		-- EXIT
		if col.other.name == "exit" then
			if #self.coins == 0 then
				g_level += 1
				if g_level == 4 then
--					scenemanager:changeScene("youwin", 5, transitions[21], easing.outBack)
					print("you win!")
					g_level = 1
				end
				scenemanager:changeScene("levelx", 2, transitions[2], easing.outBack)
			end
		end
	end

	-- anim state
	if xsprite.isonfloor then
		if xsprite.isshooting then
			xsprite.currentanim = "shoot"
		else
			if xsprite.vx == 0 then
				xsprite.currentanim = "idle"
			elseif xsprite.vx ~= 0 then
				xsprite.currentanim = "walk"
			end
		end
	else
		if xsprite.vy < 0 then
			xsprite.currentanim = "jump_up"
		elseif xsprite.vy >= 0 then
			xsprite.currentanim = "jump_down"
		end
	end

	-- keyboard handling
	if xsprite.iskeyright then
		xsprite.vx += xsprite.accel * dt
		if xsprite.vx > xsprite.maxspeed then xsprite.vx = xsprite.maxspeed end
		xsprite.flip = 1
	elseif xsprite.iskeyleft then
		xsprite.vx -= xsprite.accel * dt
		if xsprite.vx < -xsprite.maxspeed then xsprite.vx = -xsprite.maxspeed end
		xsprite.flip = -1
	else
		xsprite.vx = 0
	end
	if xsprite.iskeyup then
		if xsprite.isonfloor then
			xsprite.vy = -xsprite.jumpspeed
			xsprite.isonfloor = false
		end
	end
	if xsprite.iskeyspace then
		self:shoot()
		xsprite.vx = -96 * xsprite.flip
		xsprite.isshooting = true
		xsprite.iskeyspace = false
	end

	-- anim loop
	if xsprite.currentanim ~= "" then
		xsprite.animtimer = xsprite.animtimer - dt
		if xsprite.animtimer <= 0 then
			xsprite.frame += 1
			xsprite.animtimer = xsprite.animspeed
			if xsprite.frame > #xsprite.anims[xsprite.currentanim] then
				xsprite.frame = 1
				xsprite.isshooting = false
			end
			xsprite.bmp:setTextureRegion(xsprite.anims[xsprite.currentanim][xsprite.frame])
		end
	end

	-- move & flip
	xsprite.x = nextx
	xsprite.y = nexty
	xsprite:setPosition(xsprite.x, xsprite.y)
	xsprite.bmp:setScale(xsprite.flip, 1)
end

function LevelX:shoot()
	local sound = self.bulletsnd:play()
	sound:setVolume(0.1)
	local bullet01 = Sprite_Maker.new("bullet01", "gfx/bullets/laser_01.png", 1, 1,
			self.player1.x + (self.player1.w * self.player1.flip), self.player1.y + 16)
	bullet01:setScale(1.1)
	bullet01.vx = 2 * self.player1.accel * self.player1.flip
	bullet01.flip = self.player1.flip
	bullet01.filter = function(item, other)
		if other.name == "player1" then
			-- nothing here
		elseif other.name == "coin01" then
			-- nothing here
		elseif other.name == "enemy01" or other.name == "enemy02" then
			if math.abs(other.x - self.player1.x) < application:getContentWidth() / 2 - other.w then
				return "slide"
			else
				return "slide"
 			end
		else
			return "slide"
		end
	end
	self.bullets[#self.bullets + 1] = bullet01
	self.camera:addChild(bullet01)
	self.world:add(bullet01, bullet01.x, bullet01.y,
		bullet01.w * bullet01:getScaleX(), bullet01.h * bullet01:getScaleY())
end

function LevelX:updateBullets(xsprite, dt)
	-- mouvement
	xsprite.vx += dt
--	xsprite.vy += 50

	-- cbump
	local goalx = xsprite.x + xsprite.vx * dt
	local goaly = xsprite.y + xsprite.vy * dt
	local nextx, nexty, collisions, len = self.world:move(xsprite, goalx, goaly, xsprite.filter)
	for i = 1, len do
		local col = collisions[i]
		if col.other.name == "enemy01" or col.other.name == "enemy02" then
			if math.abs(col.other.x - self.player1.x) < application:getContentWidth() / 2 - col.other.w then
				col.other:setColorTransform(0, 1, 1, 0.5)
				col.other.lives -= 1
				col.item.lives -= 1
				col.other.vx = 96 * col.other.flip
				for t = 1, 1000000 do -- tip from pro
					t += 1
				end
			end
		else
			-- nothing here!
		end
		col.item.lives -= 1
	end

	-- move & flip
	xsprite.x = nextx
	xsprite.y = nexty
	xsprite:setPosition(xsprite.x, xsprite.y)
	xsprite.bmp:setScale(xsprite.flip, 1)
end

function LevelX:updateEnemies(xsprite, dt)
	-- pushed by gravity
	xsprite.vy += GRAVITY * dt

	-- cbump
	local goalx = xsprite.x + xsprite.vx * dt
	local goaly = xsprite.y + xsprite.vy * dt
	local nextx, nexty, collisions, len = self.world:move(xsprite, goalx, goaly, xsprite.filter)
	-- collisions
	for i = 1, len do
		local col = collisions[i]
		-- sprite collides top or bottom
		if col.other.name == "player1" then
			col.other.lives -= 1
			print("player lives: "..col.other.lives)
		end
		if col.normal.y == 1 or col.normal.y == -1 then
			col.item.vy = 0
		end
		if col.normal.x == 1 or col.normal.x == -1 then
			col.item.vx = -col.item.vx
			col.item.flip = -col.item.flip
		end
		-- sprite is on floor
		if col.normal.y == -1 then
			col.item.isonfloor = true
		end
	end

	-- anim state
	if xsprite.isonfloor then
		if xsprite.vx == 0 then
			xsprite.currentanim = "walk"
		elseif xsprite.vx ~= 0 then
			xsprite.currentanim = "walk"
		end
	end

	-- anim loop
	if xsprite.currentanim ~= "" then
		xsprite.animtimer = xsprite.animtimer - dt
		if xsprite.animtimer <= 0 then
			xsprite.frame += 1
			xsprite.animtimer = xsprite.animspeed
			if xsprite.frame > #xsprite.anims[xsprite.currentanim] then
				xsprite.frame = 1
			end
			xsprite.bmp:setTextureRegion(xsprite.anims[xsprite.currentanim][xsprite.frame])
			xsprite:setColorTransform(1, 1, 1, 1)
		end
	end

	-- move & flip
	xsprite.x = nextx
	xsprite.y = nexty
	xsprite:setPosition(xsprite.x, xsprite.y)
	xsprite.bmp:setScale(xsprite.flip, 1)
end

function LevelX:updateCollectibles(xsprite, dt)
	-- anim state
	xsprite.currentanim = "walk"

	-- anim loop
	if xsprite.currentanim ~= "" then
		xsprite.animtimer = xsprite.animtimer - dt
		if xsprite.animtimer <= 0 then
			xsprite.frame += 1
			xsprite.animtimer = xsprite.animspeed
			if xsprite.frame > #xsprite.anims[xsprite.currentanim] then
				xsprite.frame = 1
			end
			xsprite.bmp:setTextureRegion(xsprite.anims[xsprite.currentanim][xsprite.frame])
		end
	end

	-- show
	xsprite:setPosition(xsprite.x, xsprite.y)
--	xsprite.bmp:setScale(xsprite.flip, 1)
end

-- EVENT LISTENERS
function LevelX:onTransitionInBegin()
	self:addEventListener(Event.ENTER_FRAME, self.onEnterFrame, self)
end

function LevelX:onTransitionInEnd()
	self:myKeysPressed()
	-- mobile controls
--	local mymobile = MobileX.new(self.player1)
--	self:addChild(mymobile)
end

function LevelX:onTransitionOutBegin()
	self:removeEventListener(Event.ENTER_FRAME, self.onEnterFrame, self)
end

function LevelX:onTransitionOutEnd()
end

-- KEYS HANDLER
function LevelX:myKeysPressed()
	self:addEventListener(Event.KEY_DOWN, function(e)
		-- for mobiles and desktops
		if e.keyCode == KeyCode.BACK or e.keyCode == KeyCode.ESC then
--			scenemanager:changeScene("menu", 1, transitions[2], easing.outBack)
		end
	end)
end

A YouTube Video

A step by step video on how to use this platformer template. Enjoy!

https://www.youtube.com/watch?v=WPcbsgOCnLU