CBump Template

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)

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