Going Souh - Codea Cook Off code
--# Clouds | |
Clouds = class() | |
function Clouds:init(x) | |
local s = shader([[ | |
uniform mat4 modelViewProjection; | |
attribute vec4 position; | |
attribute vec4 color; | |
attribute vec2 texCoord; | |
uniform vec3 pos; | |
varying lowp vec4 vColor; | |
varying highp vec2 vTexCoord; | |
void main() { | |
vColor = color; | |
vTexCoord = texCoord; | |
if(position.y < (pos.y-1000.)) { | |
// ugly solution to hide clouds close to camera | |
gl_Position = vec4(0.); | |
} else { | |
gl_Position = modelViewProjection * position; | |
} | |
} | |
]], [[ | |
uniform lowp sampler2D texture; | |
varying lowp vec4 vColor; | |
varying highp vec2 vTexCoord; | |
void main() { | |
lowp vec4 res = texture2D(texture, vTexCoord); | |
if(res.a == 0.0) { discard; } | |
gl_FragColor = res; | |
} | |
]]) | |
local m = Mesh():up(vec3(0,0,1)):defaults() | |
math.randomseed(10) | |
for i=0,2000 do | |
local x = math.random(-500,500) | |
local y = math.random()*math.random()*400 + 60 | |
local z = 2000 - i | |
local a = math.random() * 360 | |
local size = (math.random()*math.random()*1.5 + .5)*640 | |
local p = vec3(x,z,y)*10 | |
m:rotate(a):quad(p,size) | |
end | |
self.m = m:construct(s) | |
self.m.texture = asset("GSClouds") | |
end | |
function Clouds:draw(pos) | |
self.m.shader.pos = pos | |
self.m:draw() | |
end | |
--# Intro | |
-- Intro screen showing logos with ripple effect and then info text | |
Intro = class() | |
function Intro:init() | |
self.m = mesh() | |
self.m.shader = shader("Effects:Ripple") | |
-- parameter to shader which will be animated | |
-- also use it to animate logo transparency | |
self.freq = .1 | |
self.m.texture = asset("GSTnlogy") | |
self.m:addRect(WIDTH/2,HEIGHT/2,512,256) | |
tween.delay(2, function() | |
tween(.8, self, {freq=1}, tween.easing.quadInOut, function () | |
self.m.texture = asset("GSLogo") | |
tween(.8, self, {freq=0}, tween.easing.quadInOut, | |
function () | |
tween.delay(1, function() | |
director:load(Game()) | |
self.showInfo = true | |
end) | |
end) | |
end) | |
end) | |
end | |
function Intro:draw() | |
self.m.shader.freq = self.freq | |
self.m.shader.time = ElapsedTime | |
self.m:setColors(color(255, 255, 255, (1-self.freq)*255)) | |
self.m:draw() | |
if self.showInfo then | |
-- flash info text | |
fill(222, 195, 41, 255 - math.sin(ElapsedTime*3)*140) | |
font("Futura-CondensedMedium") | |
fontSize(32) | |
text("tap to start", WIDTH/2, HEIGHT/2 - 130) | |
end | |
end | |
function Intro:touched(touch) | |
if self.showInfo then | |
tween(.4, self, {freq=1}, tween.easing.quadInOut, function () | |
director:remove(self) | |
end) | |
end | |
end | |
--# Game | |
Game = class() | |
function Game:init() | |
physics.gravity(0,0) | |
self.birds = {} | |
math.randomseed(10) | |
for i=1,20 do | |
local x,y = math.random(-400,400), math.random(-400,400)+i*200 | |
local z = math.random(-40,40) | |
local b = Bird(vec3(x,y,z)) | |
table.insert(self.birds, b) | |
end | |
-- keep a list of birds that will move on user touch | |
self.followers = {} | |
-- add the first bird as the player and position camera on it | |
local player = self.birds[1] | |
self:addFollower(player.body) | |
self.pos = player:position() | |
self.sky = Skybox() | |
self.clouds = Clouds() | |
self.cz = 9000 | |
tween(4,self, {cz=0},tween.easing.quadInOut) | |
end | |
function Game:draw() | |
perspective(45,WIDTH/HEIGHT,.1,500000) | |
camera(self.pos.x, self.pos.y-1500,self.pos.z+600+self.cz, | |
self.pos.x, self.pos.y, self.pos.z, | |
0,0,1) | |
pushMatrix() | |
translate(self.pos.x, self.pos.y, self.pos.z) | |
self.sky:draw() | |
popMatrix() | |
self.clouds:draw(self.pos) | |
-- draw flock of birds and calculate new camera center | |
local center = vec3(0,0,0) | |
for i,b in ipairs(self.birds) do | |
b:draw() | |
if b.body.info.following then | |
center = center + b:position() | |
end | |
end | |
center = center/#self.followers | |
-- move camera to new center with a short animation | |
self.pos = (self.pos*19 + center) * .05 | |
end | |
function Game:touched(touch) | |
-- move all following birds on touch event | |
self.pos.x = self.pos.x + touch.deltaX*.04 | |
self.pos.y = self.pos.y + touch.deltaY*.04 | |
local dv = vec2(touch.deltaX, touch.deltaY) | |
if dv:len() > 4 then | |
for i,b in ipairs(self.followers) do | |
b:move(dv * (1-math.random()*0.1)) | |
end | |
end | |
end | |
function Game:collide(c) | |
-- if a following bird collides with a non-following bird | |
-- then add it to the list of following birds. | |
local follows = function (b) return b.info.following end | |
local b | |
if follows(c.bodyB) and not follows(c.bodyA) then | |
b = c.bodyA | |
elseif follows(c.bodyA) and not follows(c.bodyB) then | |
b = c.bodyB | |
end | |
if b then self:addFollower(b) end | |
end | |
function Game:addFollower(f) | |
if not f.info.following then | |
f.info.following = true | |
table.insert(self.followers, f.info.bird) | |
end | |
end | |
--# Main | |
-- Birds | |
function setup() | |
displayMode(FULLSCREEN) | |
director = Director(Intro()) | |
end | |
function draw() | |
director:draw() | |
end | |
function touched(touch) | |
director:touched(touch) | |
end | |
function collide(contact) | |
director:collide(contact) | |
end | |
--# Item | |
Item = class() | |
-- get and set angle with vec2 instead of angle | |
function Item:getDir() | |
return vec2(0,1):rotate(math.rad(-self.body.angle)) | |
end | |
function Item:setDir(dir) | |
self.body.angle = math.deg(dir:angleBetween(vec2(0,1))) | |
end | |
function Item:destroy() | |
self.body:destroy() | |
self.body = nil | |
end | |
--# Bird | |
Bird = class(Item) | |
function Bird:init(pos) | |
local w,h = 50,80 -- wing size | |
local t = asset("GSWing") | |
-- discard pixels with alpha 0, slow but easy fix. | |
local s = shader("Basic:Blend Images") | |
s.fragmentProgram = [[ | |
uniform lowp sampler2D texture; | |
varying lowp vec4 vColor; | |
varying highp vec2 vTexCoord; | |
void main() { | |
lowp vec4 res = texture2D(texture, vTexCoord); | |
if(res.a == 0.0) { discard; } | |
gl_FragColor = res * vColor; | |
} | |
]] | |
self.left = mesh() | |
self.left.texture = t | |
self.left.shader = s | |
self.left:addRect(w/2,0,w,h) | |
self.left:setRectTex(1, -1,1,-1,1) | |
self.right = mesh() | |
self.right.texture = t | |
self.right.shader = s | |
self.right:addRect(-w/2,0,w,h) | |
-- run a looping tween animating the wing. start it at random time | |
-- with tween.delay to make the birds seem more individual | |
self.a = 0 | |
tween.delay(math.random(0,2), function() | |
tween(0.4, self, {a=80}, | |
{loop=tween.loop.pingpong,tween=tween.easing.quadOutIn}) | |
end) | |
self.body = physics.body(CIRCLE, 40) | |
self.body.angularDamping = 4 | |
self.body.linearDamping = 1 | |
-- self.body.restitution = .5 | |
self.body.x, self.body.y = pos.x, pos.y | |
self.body.info = {bird=self} | |
self.z = pos.z | |
self.tail = mesh() | |
self.tail.texture = asset("GSTail") | |
self.tail.shader = s | |
self.tail:addRect(0,-15,10,30) | |
end | |
function Bird:position() | |
return vec3(self.body.x, self.body.y, self.z) | |
end | |
function Bird:draw() | |
-- position bird | |
pushMatrix() | |
translate(self.body.x, self.body.y, self.z) | |
rotate(-self.body.angle) | |
-- draw wings | |
pushMatrix() | |
rotate(-self.a, 0,1,0) | |
self.left:draw() | |
popMatrix() | |
pushMatrix() | |
rotate(self.a, 0,1,0) | |
self.right:draw() | |
popMatrix() | |
-- draw the five tail feathers, expanded depending on velocity | |
translate(0,-7) | |
local v = 20-self.body.linearVelocity:len()*.1 | |
v = math.min(math.max(v,5), 20) | |
for i=-2,2 do | |
pushMatrix() | |
rotate(i*v) | |
self.tail:draw() | |
popMatrix() | |
end | |
popMatrix() | |
end | |
function Bird:move(dv) | |
self.body.linearVelocity = dv*50 | |
if dv:len() > 1 then | |
self:setDir((self:getDir() + dv:normalize()*2):normalize()) | |
end | |
end | |
--# Director | |
Director = class() | |
function Director:init(scene) | |
self.scenes = {} | |
self:load(scene) | |
end | |
function Director:load(scene) | |
table.insert(self.scenes, 1, scene) | |
end | |
function Director:remove(scene) | |
for i,v in ipairs(self.scenes) do | |
if v == scene then | |
return table.remove(self.scenes, i) | |
end | |
end | |
end | |
function Director:draw() | |
background(0, 0, 0, 255) | |
local c = #self.scenes > 1 | |
for i,scene in ipairs(self.scenes) do | |
scene:draw() | |
if c then | |
-- reset projection to default between scenes | |
ortho() | |
viewMatrix(matrix()) | |
end | |
end | |
end | |
function Director:touched(touch) | |
for i,scene in ipairs(self.scenes) do | |
scene:touched(touch) | |
end | |
end | |
function Director:collide(contact) | |
for i,scene in ipairs(self.scenes) do | |
if scene.collide then | |
scene:collide(contact) | |
end | |
end | |
end | |
--# Assets | |
-- utility function to download assets for the first run. | |
-- returns an empty image with the correct size while waiting | |
-- for http.request result. | |
local baseUrl = "http://goingsouth.meteor.com/" | |
local c = ContentScaleFactor | |
local imageMap = { | |
GSWing = {256,256,"wing.png"}, | |
GSTail = {64,256,"tailfeather.png"}, | |
GSTnlogy = {512,256,"tnlogy.png"}, | |
GSLogo = {512,256,"goingsouth.png"}, | |
GSClouds = {256,256, "clouds.png"}, | |
-- Using skybox graphics by Jochum Skoglund | |
GSSkynx = {c*512,c*512,"skybox/nx.jpg"}, | |
GSSkyny = {c*512,c*512,"skybox/ny.jpg"}, | |
GSSkynz = {c*512,c*512,"skybox/nz.jpg"}, | |
GSSkypx = {c*512,c*512,"skybox/px.jpg"}, | |
GSSkypy = {c*512,c*512,"skybox/py.jpg"}, | |
GSSkypz = {c*512,c*512,"skybox/pz.jpg"}, | |
} | |
function asset(name) | |
local img = readImage("Documents:" .. name) | |
if img == nil then | |
local w,h,url = unpack(imageMap[name] or {1,1}) | |
img = image(w,h) | |
if url then | |
http.request(baseUrl .. url, function (data) | |
pushStyle() | |
setContext(img) | |
spriteMode(CORNER) | |
sprite(data,0,0,w,h) | |
setContext() | |
popStyle() | |
saveImage("Documents:" .. name, data) | |
end) | |
end | |
end | |
return img | |
end | |
--# Skybox | |
Skybox = class() | |
function Skybox:init() | |
local s = 20000 | |
local hs = s/2 | |
self.ms = { | |
Mesh("GSSkyny"):quad(vec3(0,0,-hs),s):construct(), | |
Mesh("GSSkypy"):up(vec3(0,-1,0)): | |
quad(vec3(0,0,hs),s):construct(), | |
Mesh("GSSkynz"):right(vec3(-1,0,0)): | |
up(vec3(0,0,1)):quad(vec3(0,-hs,0),s):construct(), | |
Mesh("GSSkypz"):up(vec3(0,0,1)): | |
quad(vec3(0,hs,0),s):construct(), | |
Mesh("GSSkypx"):right(vec3(0,-1,0)):up(vec3(0,0,1)): | |
quad(vec3(hs,0,0),s):construct(), | |
Mesh("GSSkynx"):right(vec3(0,1,0)):up(vec3(0,0,1)): | |
quad(vec3(-hs,0,0),s):construct() | |
} | |
end | |
function Skybox:draw() | |
for i,v in ipairs(self.ms) do | |
v:draw() | |
end | |
end | |
function Skybox:touched(touch) | |
end | |
--# Mesh | |
Mesh = class() | |
function Mesh:init(texture) | |
self.vs, self.uvs = {}, {} | |
self.buffers = {} | |
self.texture = texture | |
self.ds = { | |
right = vec3(1,0,0), | |
up = vec3(0,1,0), | |
size = vec2(1,1), | |
center = true | |
} | |
self:reset() | |
end | |
function Mesh:reset() | |
self.ps = {} | |
for k,v in pairs(self.ds) do | |
self.ps[k] = v | |
end | |
return self | |
end | |
function Mesh:defaults() | |
for k,v in pairs(self.ps) do | |
self.ds[k] = v | |
end | |
return self | |
end | |
function Mesh:buffer(name, val, n) | |
if not self.buffers[name] then | |
self.buffers[name] = {} | |
end | |
for i=1,n do | |
table.insert(self.buffers[name], val) | |
end | |
return self | |
end | |
function Mesh:right(v) self.ps.right = v;return self end | |
function Mesh:up(v) self.ps.up = v;return self end | |
function Mesh:size(v) self.ps.size = v;return self end | |
function Mesh:rotate(a) self.ps.rotate = a;return self end | |
function mult(m, v) -- matrix multiplication | |
return vec3( | |
m[1]*v.x+m[2]*v.y+m[3]*v.z, | |
m[5]*v.x+m[6]*v.y+m[7]*v.z, | |
m[9]*v.x+m[10]*v.y+m[11]*v.z | |
) | |
end | |
function Mesh:quad(pos, w, h) | |
local ps = self.ps | |
w,h = w or ps.size.x, h or w or ps.size.y | |
local right = ps.right * w | |
local up = ps.up * h | |
if ps.rotate then | |
local n = right:cross(up):normalize() | |
local m = matrix():rotate(ps.rotate, n.x,n.y,n.z) | |
right, up = mult(m,right), mult(m,up) | |
end | |
if ps.center then | |
pos = pos - (right+up)*.5 | |
end | |
table.insert(self.vs, pos) | |
table.insert(self.vs, pos+right) | |
table.insert(self.vs, pos+up) | |
table.insert(self.vs, pos+right) | |
table.insert(self.vs, pos+up+right) | |
table.insert(self.vs, pos+up) | |
table.insert(self.uvs, vec2(0,0)) | |
table.insert(self.uvs, vec2(1,0)) | |
table.insert(self.uvs, vec2(0,1)) | |
table.insert(self.uvs, vec2(1,0)) | |
table.insert(self.uvs, vec2(1,1)) | |
table.insert(self.uvs, vec2(0,1)) | |
self:reset() | |
return self | |
end | |
function Mesh:construct(s) | |
local m = mesh() | |
if s then m.shader = s end | |
m.vertices, m.texCoords = self.vs, self.uvs | |
if self.texture then | |
m.texture = asset(self.texture) | |
end | |
for k,v in pairs(self.buffers) do | |
(m:buffer(k)):set(v) | |
end | |
m:setColors(color(255, 255, 255, 255)) | |
return m | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment