-- meteor_shower.lua -- by Thatcher Ulrich November 2001 -- This source code has been donated to the Public Domain. Do -- whatever you want with it. -- Sample SDL game written in Lua. -- This game requires the luaSDL binding. if loadmodule then loadmodule("SDL") end SDL.SDL_BlitSurface = SDL.SDL_UpperBlit; -- keep a table of previously-loaded sprites, to maximize sharing. sprite_cache = {} function sprite(file) -- sprite constructor. Pass in a bitmap filename. Returns an SDL_Surface which -- is ready to be blitted. if sprite_cache[file] then return sprite_cache[file] end local temp, s; -- Load the sprite image s = SDL.SDL_LoadBMP("icon.bmp"); if s == nil then print("Couldn't load " .. file .. ": " .. SDL.SDL_GetError()); return nil end -- Set transparent color to black (?) SDL.SDL_SetColorKey(s, SDL.bit_or(SDL.SDL_SRCCOLORKEY, SDL.SDL_RLEACCEL), 0) -- Convert sprite to video format temp = SDL.SDL_DisplayFormat(s); SDL.SDL_FreeSurface(s); if temp == nil then print("Couldn't convert background: " .. SDL.SDL_GetError()); return nil end s = temp; sprite_cache[file] = s return s end function show_sprite(screen, sprite, x, y) -- make sure we have a temporary rect structure if not temp_rect then temp_rect = SDL.SDL_Rect_new() end temp_rect.x = x - sprite.w / 2 temp_rect.y = y - sprite.h / 2 temp_rect.w = sprite.w temp_rect.h = sprite.h SDL.SDL_BlitSurface(sprite, NULL, screen, temp_rect) end -- -- vec2 type -- vec2_tag = nil -- be sure to re-init the vector type if we reload function vec2(t) -- constructor if not vec2_tag then vec2_tag = newtag() settagmethod(vec2_tag, "add", function (a, b) return vec2{ a.x + b.x, a.y + b.y } end ) settagmethod(vec2_tag, "sub", function (a, b) return vec2{ a.x - b.x, a.y - b.y } end ) settagmethod(vec2_tag, "mul", function (a, b) if tonumber(a) then return vec2{ a * b.x, a * b.y } elseif tonumber(b) then return vec2{ a.x * b, a.y * b } else -- dot product. return (a.x * b.x) + (a.y * b.y) end end ) settagmethod(vec2_tag, "unm", function (a) return vec2{ -a.x, -a.y } end ) end local v = {} if type(t) == 'table' or tag(t) == vec2_tag then v.x = tonumber(t[1]) or tonumber(t.x) or 0 v.y = tonumber(t[2]) or tonumber(t.y) or 0 else v.x = 0 v.y = 0 end settag(v, vec2_tag) v.normalize = vec2_normalize return v end gamestate = { event_buffer = nil, last_update_ticks = 0, begin_time = 0, elapsed_ticks = 0, frames = 0, update_period = 33, -- interval between calls to update_tick(), in ms active = 1, screen = nil, background = nil, actors = {}, add_actor = function(self, a) assert(a) tinsert(self.actors, a) end } function handle_event(event) -- called by main loop when it detects an SDL event. if event.type == SDL.SDL_KEYDOWN then local sym = event.key.keysym.sym if sym == SDL.SDLK_q or sym == SDL.SDLK_ESCAPE then gamestate.active = nil elseif sym == SDL.SDLK_s then -- take a screenshot... SDL.SDL_SaveBMP(gamestate.screen, "screenshot.bmp") end elseif event.type == SDL.SDL_QUIT then gamestate.active = nil end end function update_tick() -- called at regular intervals, to update the game state. -- call the update handler for each actor for i = 1, getn(gamestate.actors) do gamestate.actors[i]:update(gamestate) end end function render_frame(screen, background) -- called to render a new frame. -- clear screen SDL.SDL_FillRect(screen, NULL, background); -- draw the actors for i = 1, getn(gamestate.actors) do gamestate.actors[i]:update() gamestate.actors[i]:render(screen) end -- flip SDL.SDL_UpdateRect(screen, 0, 0, 0, 0) end function gameloop_iteration() -- call this to update the game state. Runs update ticks and renders -- according to elapsed time. if gamestate.event_buffer == nil then gamestate.event_buffer = SDL.SDL_Event_new() end -- consume any pending events while SDL.SDL_PollEvent(gamestate.event_buffer) ~= 0 do handle_event(gamestate.event_buffer) end render_frame(gamestate.screen, gamestate.background) gamestate.frames = gamestate.frames + 1 end function engine_init(argv) local width, height; local video_bpp; local videoflags; local i -- Initialize SDL if SDL.SDL_Init(SDL.SDL_INIT_VIDEO) < 0 then print("Couldn't initialize SDL: ",SDL.SDL_GetError()); exit(1); end videoflags = SDL.bit_or(SDL.SDL_HWSURFACE, SDL.SDL_ANYFORMAT) -- SDL.SDL_HWSURFACE was SDL.SDL_SWSURFACE; any benefit to that? width = 640 height = 480 video_bpp = 16 argv = argv or {} for i = 1, getn(argv) do if argv[i] == "-width" then width = tonumber(argv[i+1]); i = i + 1 elseif argv[i] == "-height" then height = tonumber(argv[i+1]); i = i + 1 elseif argv[i] == "-fullscreen" then videoflags = SDL.bit_or(videoflags, SDL.SDL_FULLSCREEN) _ = [[ elseif if ( strcmp(argv[argc-1], "-bpp") == 0 ) { video_bpp = atoi(argv[argc]); videoflags = SDL.bit_and(videoflags, ~SDL.SDL_ANYFORMAT); --argc; } else if ( strcmp(argv[argc], "-fast") == 0 ) { videoflags = FastestFlags(videoflags, width, height, video_bpp); } else if ( strcmp(argv[argc], "-hw") == 0 ) { videoflags ^= SDL.SDL_HWSURFACE; } else if ( strcmp(argv[argc], "-flip") == 0 ) { videoflags ^= SDL.SDL_DOUBLEBUF; } else if ( isdigit(argv[argc][0]) ) { numsprites = atoi(argv[argc]); ]]; _ = nil; else print("Usage: main_program [-bpp N] [-hw] [-flip] [-fast] [-fullscreen] [numsprites]\n"); exit(1); end end -- Set video mode gamestate.screen = SDL.SDL_SetVideoMode(width, height, video_bpp, videoflags); if gamestate.screen == nil then print("Couldn't set ", width, "x", height, " video mode: ", SDL.SDL_GetError()); exit(2); end gamestate.background = SDL.SDL_MapRGB(gamestate.screen.format, 0, 0, 0); SDL.SDL_ShowCursor(0) -- Print out information about our surfaces print("Screen is at ", gamestate.screen.format.BitsPerPixel, " bits per pixel\n"); if SDL.bit_and(gamestate.screen.flags, SDL.SDL_HWSURFACE) == SDL.SDL_HWSURFACE then print("Screen is in video memory"); else print("Screen is in system memory"); end if SDL.bit_and(gamestate.screen.flags, SDL.SDL_DOUBLEBUF) == SDL.SDL_DOUBLEBUF then print("Screen has double-buffering enabled"); end -- init timer gamestate.begin_time = SDL.SDL_GetTicks(); gamestate.last_update_ticks = gamestate.begin_time; end function engine_loop() -- Loop, blitting sprites and waiting for a keystroke while gamestate.active do gameloop_iteration() end -- clean up if event_buffer then SDL.SDL_Event_delete(event) end -- Print out some timing information local current_time = SDL.SDL_GetTicks(); if current_time > gamestate.begin_time then print((gamestate.frames * 1000) / (current_time - gamestate.begin_time), " frames per second\n"); end SDL.SDL_Quit(); end engine_init{ -- "-fullscreen" } -- -- game-specific stuff -- -- -- actor type -- function actor_update(self, gs) -- update this actor. Default does linear motion according to velocity. -- gs is the gamestate self.position = self.position + self.velocity -- wrap around at screen edge if self.position.x < 0 or self.position.x > 608 then self.velocity.x = -self.velocity.x self.position.x = self.position.x + self.velocity.x end if self.position.y < 0 or self.position.y > 448 then self.velocity.y = -self.velocity.y self.position.y = self.position.y + self.velocity.y end end function actor_render(self, screen) -- draw this actor. Default implementation blits the actor's sprite -- to the screen at the actor's position. show_sprite(screen, self.sprite, self.position.x, self.position.y) end function actor(t) -- actor constructor. Pass in the name of a sprite bitmap. local a = {} -- copy elements of t for k,v in t do a[k] = v end a.type = "actor" a.active = 1 a.sprite = t.sprite a.position = vec2(t.position) a.velocity = vec2(t.velocity) a.radius = a.radius or (a.sprite and a.sprite.w * 0.5) or 0 a.update = actor_update a.render = actor_render return a end -- -- initialize the game -- -- Generate a bunch of asteroids for i = 1,100 do gamestate:add_actor( actor{ sprite = sprite("icon.bmp"), position = { random(gamestate.screen.w), random(gamestate.screen.h) }, velocity = { (random()*3 - 2), (random()*3 - 2) }, -- pixels/sec size = 1 } ) end -- run the game engine_loop()