CBump Template
From GiderosMobile
Revision as of 21:19, 8 December 2019 by MoKaLux (talk | contribs) (→Using CBump and TILED (handles collisions))
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!