Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
233 lines (202 sloc) 5.83 KB
# Encoding: UTF-8
# A simple jump-and-run/platformer game with a tile-based map.
# Shows how to
# * implement jumping/gravity
# * implement scrolling using Window#translate
# * implement a simple tile-based map
# * load levels from primitive text files
# Some exercises, starting at the real basics:
# 0) understand the existing code!
# As shown in the tutorial:
# 1) change it use Gosu's Z-ordering
# 2) add gamepad support
# 3) add a score as in the tutorial game
# 4) similarly, add sound effects for various events
# Exploring this game's code and Gosu:
# 5) make the player wider, so he doesn't fall off edges as easily
# 6) add background music (check if playing in Window#update to implement
# looping)
# 7) implement parallax scrolling for the star background!
# Getting tricky:
# 8) optimize Map#draw so only tiles on screen are drawn (needs modulo, a pen
# and paper to figure out)
# 9) add loading of next level when all gems are collected
# ...Enemies, a more sophisticated object system, weapons, title and credits
# screens...
require "gosu"
WIDTH, HEIGHT = 640, 480
module Tiles
Grass = 0
Earth = 1
end
class CollectibleGem
attr_reader :x, :y
def initialize(image, x, y)
@image = image
@x, @y = x, y
end
def draw
# Draw, slowly rotating
@image.draw_rot(@x, @y, 0, 25 * Math.sin(Gosu.milliseconds / 133.7))
end
end
# Player class.
class Player
attr_reader :x, :y
def initialize(map, x, y)
@x, @y = x, y
@dir = :left
@vy = 0 # Vertical velocity
@map = map
# Load all animation frames
@standing, @walk1, @walk2, @jump = *Gosu::Image.load_tiles("media/cptn_ruby.png", 50, 50)
# This always points to the frame that is currently drawn.
# This is set in update, and used in draw.
@cur_image = @standing
end
def draw
# Flip vertically when facing to the left.
if @dir == :left
offs_x = -25
factor = 1.0
else
offs_x = 25
factor = -1.0
end
@cur_image.draw(@x + offs_x, @y - 49, 0, factor, 1.0)
end
# Could the object be placed at x + offs_x/y + offs_y without being stuck?
def would_fit(offs_x, offs_y)
# Check at the center/top and center/bottom for map collisions
not @map.solid?(@x + offs_x, @y + offs_y) and
not @map.solid?(@x + offs_x, @y + offs_y - 45)
end
def update(move_x)
# Select image depending on action
if (move_x == 0)
@cur_image = @standing
else
@cur_image = (Gosu.milliseconds / 175 % 2 == 0) ? @walk1 : @walk2
end
if (@vy < 0)
@cur_image = @jump
end
# Directional walking, horizontal movement
if move_x > 0
@dir = :right
move_x.times { if would_fit(1, 0) then @x += 1 end }
end
if move_x < 0
@dir = :left
(-move_x).times { if would_fit(-1, 0) then @x -= 1 end }
end
# Acceleration/gravity
# By adding 1 each frame, and (ideally) adding vy to y, the player's
# jumping curve will be the parabole we want it to be.
@vy += 1
# Vertical movement
if @vy > 0
@vy.times { if would_fit(0, 1) then @y += 1 else @vy = 0 end }
end
if @vy < 0
(-@vy).times { if would_fit(0, -1) then @y -= 1 else @vy = 0 end }
end
end
def try_to_jump
if @map.solid?(@x, @y + 1)
@vy = -20
end
end
def collect_gems(gems)
# Same as in the tutorial game.
gems.reject! do |c|
(c.x - @x).abs < 50 and (c.y - @y).abs < 50
end
end
end
# Map class holds and draws tiles and gems.
class Map
attr_reader :width, :height, :gems
def initialize(filename)
# Load 60x60 tiles, 5px overlap in all four directions.
@tileset = Gosu::Image.load_tiles("media/tileset.png", 60, 60, tileable: true)
gem_img = Gosu::Image.new("media/gem.png")
@gems = []
lines = File.readlines(filename).map { |line| line.chomp }
@height = lines.size
@width = lines[0].size
@tiles = Array.new(@width) do |x|
Array.new(@height) do |y|
case lines[y][x, 1]
when '"'
Tiles::Grass
when '#'
Tiles::Earth
when 'x'
@gems.push(CollectibleGem.new(gem_img, x * 50 + 25, y * 50 + 25))
nil
else
nil
end
end
end
end
def draw
# Very primitive drawing function:
# Draws all the tiles, some off-screen, some on-screen.
@height.times do |y|
@width.times do |x|
tile = @tiles[x][y]
if tile
# Draw the tile with an offset (tile images have some overlap)
# Scrolling is implemented here just as in the game objects.
@tileset[tile].draw(x * 50 - 5, y * 50 - 5, 0)
end
end
end
@gems.each { |c| c.draw }
end
# Solid at a given pixel position?
def solid?(x, y)
y < 0 || @tiles[x / 50][y / 50]
end
end
class CptnRuby < (Example rescue Gosu::Window)
def initialize
super WIDTH, HEIGHT
self.caption = "Cptn. Ruby"
@sky = Gosu::Image.new("media/space.png", tileable: true)
@map = Map.new("media/cptn_ruby_map.txt")
@cptn = Player.new(@map, 400, 100)
# The scrolling position is stored as top left corner of the screen.
@camera_x = @camera_y = 0
end
def update
move_x = 0
move_x -= 5 if Gosu.button_down? Gosu::KB_LEFT
move_x += 5 if Gosu.button_down? Gosu::KB_RIGHT
@cptn.update(move_x)
@cptn.collect_gems(@map.gems)
# Scrolling follows player
@camera_x = [[@cptn.x - WIDTH / 2, 0].max, @map.width * 50 - WIDTH].min
@camera_y = [[@cptn.y - HEIGHT / 2, 0].max, @map.height * 50 - HEIGHT].min
end
def draw
@sky.draw 0, 0, 0
Gosu.translate(-@camera_x, -@camera_y) do
@map.draw
@cptn.draw
end
end
def button_down(id)
case id
when Gosu::KB_UP
@cptn.try_to_jump
when Gosu::KB_ESCAPE
close
else
super
end
end
end
CptnRuby.new.show if __FILE__ == $0
You can’t perform that action at this time.