Difference between revisions of "CBump Template"
From GiderosMobile
(added content) |
|||
(16 intermediate revisions by 3 users not shown) | |||
Line 1: | Line 1: | ||
__TOC__ | __TOC__ | ||
+ | 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. | 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.<br/> | ||
+ | The class handles tile collisions automatically. | ||
− | + | <syntaxhighlight lang="lua"> | |
− | |||
− | |||
-- usage | -- usage | ||
--local map = CBumpTiled.new("tiled/test.lua", world, bg, true) | --local map = CBumpTiled.new("tiled/test.lua", world, bg, true) | ||
-- where world is the cbump world | -- where world is the cbump world | ||
− | -- bg is a sprite layer (here a background sprite) | + | -- bg is a sprite layer (here a background sprite layer) |
− | -- true | + | -- true if only foreground is added to cbump collision world |
CBumpTiled = Core.class(Sprite) | CBumpTiled = Core.class(Sprite) | ||
Line 37: | Line 30: | ||
function CBumpTiled:init(filename, xworld, xlayer, xtop) | 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) | -- 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_HORIZONTALLY_FLAG = 0x80000000; | ||
Line 60: | Line 52: | ||
if gid ~= 0 then | if gid ~= 0 then | ||
-- Read flipping flags | -- Read flipping flags | ||
− | flipHor = | + | flipHor = gid & FLIPPED_HORIZONTALLY_FLAG |
− | flipVer = | + | flipVer = gid & FLIPPED_VERTICALLY_FLAG |
− | flipDia = | + | flipDia = gid & FLIPPED_DIAGONALLY_FLAG |
-- Convert flags to gideros style | -- Convert flags to gideros style | ||
if(flipHor ~= 0) then flipHor = TileMap.FLIP_HORIZONTAL end | if(flipHor ~= 0) then flipHor = TileMap.FLIP_HORIZONTAL end | ||
Line 68: | Line 60: | ||
if(flipDia ~= 0) then flipDia = TileMap.FLIP_DIAGONAL end | if(flipDia ~= 0) then flipDia = TileMap.FLIP_DIAGONAL end | ||
-- Clear the flags from gid so other information is healthy | -- Clear the flags from gid so other information is healthy | ||
− | gid = | + | gid = gid & ~ ( |
− | + | FLIPPED_HORIZONTALLY_FLAG | | |
− | + | FLIPPED_VERTICALLY_FLAG | | |
− | + | FLIPPED_DIAGONALLY_FLAG | |
− | |||
− | |||
− | |||
− | |||
− | |||
) | ) | ||
end | end | ||
Line 100: | Line 87: | ||
local ty = math.floor((gid - tileset.firstgid) / tileset.sizex) + 1 | local ty = math.floor((gid - tileset.firstgid) / tileset.sizex) + 1 | ||
-- Set the tile with flip info | -- Set the tile with flip info | ||
− | tilemap:setTile(x, y, tx, ty, | + | tilemap:setTile(x, y, tx, ty, (flipHor | flipVer | flipDia)) |
xlayer:addChild(tilemap) | xlayer:addChild(tilemap) | ||
xlayer:setAlpha(layer.opacity) | xlayer:setAlpha(layer.opacity) | ||
Line 128: | Line 115: | ||
end | end | ||
end | end | ||
− | </ | + | </syntaxhighlight> |
− | + | '''Usage''' | |
− | |||
This is an example of how to set up CBump and TILED. | This is an example of how to set up CBump and TILED. | ||
− | + | <syntaxhighlight lang="lua"> | |
− | |||
− | < | ||
-- cbump | -- cbump | ||
local cbump = require "cbump" | local cbump = require "cbump" | ||
− | local world = | + | -- Constructor: BumpWorld.new([cellSize = 64]) |
+ | local world = BumpWorld.new() | ||
-- camera | -- camera | ||
local camera = Sprite.new() | local camera = Sprite.new() | ||
local bg = Sprite.new() | local bg = Sprite.new() | ||
− | |||
local fg = Sprite.new() | local fg = Sprite.new() | ||
− | |||
camera:addChild(bg) | camera:addChild(bg) | ||
camera:addChild(fg) | camera:addChild(fg) | ||
Line 153: | Line 136: | ||
-- tiled | -- tiled | ||
local map = CBumpTiled.new("tiled/test.lua", world, bg, true) | local map = CBumpTiled.new("tiled/test.lua", world, bg, true) | ||
− | </ | + | </syntaxhighlight> |
− | < | + | |
− | < | + | === 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! | ||
+ | <syntaxhighlight lang="lua"> | ||
+ | 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 | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | === A YouTube Video === | ||
+ | A step by step video on how to use this platformer template. Enjoy! | ||
+ | |||
+ | <youtube>https://www.youtube.com/watch?v=WPcbsgOCnLU</youtube> | ||
+ | |||
+ | |||
+ | {{GIDEROS IMPORTANT LINKS}} |
Latest revision as of 22:54, 18 November 2023
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"
-- Constructor: BumpWorld.new([cellSize = 64])
local world = BumpWorld.new()
-- 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!