Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- require "gosu"
- require "singleton"
- SCREEN_WIDTH = 640
- SCREEN_HEIGHT = 480
- MAP_WIDTH = 640
- MAP_HEIGHT = 3838
- SHIP_SPEED = 12
- # TODO:
- # Use TexPlay to fix performance issues regarding printing the map
- # This might be solved using the to_blob method on a loaded image of the map
- # And then splicing that image every time we need to draw it
- # Instead of reloading the same image every game tick, which causes lagg
- # Handle Game-Over and Game-Win scenarios
- # Fix bullet movement function so it can damage the player
- # Create additional enemies and add them to the fated enemy queue
- # Find a way to distribute this game without the host computer having Ruby
- class Box
- attr_accessor :x, :y
- attr_reader :w, :h
- def initialize(x,y,w,h)
- @x, @y, @w, @h = x, y, w, h
- end
- # AABB collision detection with other boxes.
- def collidesWith?(o)
- overlap = lambda { |ap, ad, bp, bd|
- ((ap + ad >= bp) and (bp >= ap)) or ((bp + bd >= ap) and (ap >= bp))
- }
- overlap.(x, w, o.x, o.w) and overlap.(y, h, o.y, o.h)
- end
- # AABB swept collision detection with another box with velocities vx and vy
- def inPathOf?(o, vx, vy)
- # Collision detection for one-dimensional paths
- return collidesWith?(Box.new(o.x, o.y, o.w, o.h + vy)) if vx.zero?
- return collidesWith?(Box.new(o.x, o.y, o.w + vx, o.h)) if vy.zero?
- # Collision detection for beginning and end of path
- return true if collidesWith?(o)
- return true if collidesWith?(Box.new(o.x + vx, o.y + vy, o.w, o.h))
- # Distances to enter and exit a collision on an axis
- # Entry = Distance between closest points, Exit = between farthest
- xInvEn = ((vx > 0) ? (@x - (o.x + o.w)) : ((@x + @w) - o.x))
- xInvEx = ((vx > 0) ? ((@x + @w) - o.x) : (@x - (o.x + o.w)))
- yInvEn = ((vy > 0) ? (@y - (o.y + o.h)) : ((@y + @h) - o.y))
- yInvEx = ((vy > 0) ? ((@y + @h) - o.y) : (@y - (o.y + o.h)))
- # Collision times relative to entry and exit
- # 0 = collision at start of movement, 1 = collision at end, 0.5 = halfway, etc...
- xEntry = xInvEntry / vx.to_f
- xExit = xInvExit / vx.to_f
- yEntry = yInvEntry / vx.to_f
- yExit = yInvExit / vx.to_f
- entryTime = [xEntry, yEntry].max
- exitTime = [xExit, yExit].min
- return !((entryTime > exitTime) or ((xEntry < 0.0) and (yEntry < 0.0)) or (xEntry > 1.0) or (yEntry > 1.0))
- end
- end
- class Bullet
- attr_reader :hitbox
- def initialize(img, dbox, hbox, vx, vy, enemy=true)
- @image = img
- @drawbox = dbox
- @hitbox = hbox
- @vx = vx
- @vy = vy
- @isEnemy = enemy
- end
- def destroy
- $bulletQueue.remove(self)
- end
- def draw
- @image.draw(@drawbox.x, @drawbox.y, 0)
- end
- def update
- shouldDie = false
- moved = true
- if @isEnemy then
- puts "Enemy bullet fired" # Temporary
- else
- $liveEnemyQueue.each do |enemy|
- if enemy.hitbox.inPathOf?(@hitbox, @vx, @vy) then
- moved = false
- shouldDie = true
- enemy.takeHit
- end
- end
- end
- if moved then
- @hitbox.x += @vx
- @hitbox.y += @vy
- @drawbox.x += @vx
- @drawbox.y += @vy
- end
- destroy if ((@drawbox.x < 0) or (@drawbox.y < 0) or (@drawbox.x > SCREEN_WIDTH) or (@drawbox.y > SCREEN_HEIGHT)) or shouldDie
- end
- end
- class Enemy
- attr_reader :hitbox
- def initialize(img, dbox, hbox, hp)
- @image = img
- @drawbox = dbox
- @hitbox = hbox
- @hitpoints = hp
- end
- def draw
- @image.draw(@drawbox.x, @drawbox.y, 0)
- end
- def move(distX, distY)
- # Bounds checking
- distX = -(@drawbox.x) if (@drawbox.x + distX < 0)
- distX = (SCREEN_WIDTH - (@drawbox.x + @drawbox.w)) if (@drawbox.x + @drawbox.w + distX > SCREEN_WIDTH)
- distY = -(@drawbox.y) if (@drawbox.y + distY < 0)
- distY = (SCREEN_HEIGHT - (@drawbox.y + @drawbox.h)) if (@drawbox.y + @drawbox.h + distY > SCREEN_HEIGHT)
- # Moving the unit
- @hitbox.x += distX
- @hitbox.y += distY
- @drawbox.x += distX
- @drawbox.y += distY
- # Collision detection may happen here
- end
- def takeHit
- @hitpoints -= 1
- if @hitpoints <= 0 then
- $deadEnemyQueue.add(self)
- $liveEnemyQueue.remove(self)
- end
- end
- end
- class Player
- include Singleton
- attr_reader :hitbox
- def initialize
- @drawbox = Box.new(272, 399, 96, 80)
- @hitbox = Box.new(304, 399, 30, 80)
- @lives = 3
- end
- def draw
- @image.draw(@drawbox.x, @drawbox.y, 0) unless @image.nil?
- end
- def fireBullet
- $bulletQueue.add(Bullet.new(@pBullet, Box.new(@drawbox.x + 28, @drawbox.y + 12, @bWidth, @bHeight),
- Box.new(@drawbox.x + 28, @drawbox.y + 12, @bWidth, @bHeight), 0, -20, false))
- $bulletQueue.add(Bullet.new(@pBullet, Box.new(@drawbox.x + 58, @drawbox.y + 12, @bWidth, @bHeight),
- Box.new(@drawbox.x + 58, @drawbox.y + 12, @bWidth, @bHeight), 0, -20, false))
- end
- def giveWindow(window)
- @window = window
- @image = Gosu::Image.new(@window, "./Ship.png", false, 0, 0, 96, 80)
- @bWidth = 8
- @bHeight = 14
- @pBullet = Gosu::Image.new(@window, "./PlayerBullet.png", false, 0, 0, @bWidth, @bHeight)
- end
- def takeHit
- @lives -= 1
- # game over effects here
- end
- def update
- distX = 0
- distY = 0
- moved = false
- $actionQueue.each do |action|
- case action
- when Gosu::KbLeft
- distX -= SHIP_SPEED
- moved = true
- when Gosu::KbUp
- distY -= SHIP_SPEED
- moved = true
- when Gosu::KbRight
- distX += SHIP_SPEED
- moved = true
- when Gosu::KbDown
- distY += SHIP_SPEED
- moved = true
- when Gosu::KbSpace
- fireBullet
- end
- end
- if moved
- # Bounds checking
- distX = -(@drawbox.x) if (@drawbox.x + distX < 0)
- distX = (SCREEN_WIDTH - (@drawbox.x + @drawbox.w)) if (@drawbox.x + @drawbox.w + distX > SCREEN_WIDTH)
- distY = -(@drawbox.y) if (@drawbox.y + distY < 0)
- distY = (SCREEN_HEIGHT - (@drawbox.y + @drawbox.h)) if (@drawbox.y + @drawbox.h + distY > SCREEN_HEIGHT)
- end
- @hitbox.x += distX
- @hitbox.y += distY
- @drawbox.x += distX
- @drawbox.y += distY
- # Handle any coollision detection here maybe?
- end
- end
- class SetQueue
- def initialize(*elements)
- @ary = elements.uniq
- end
- def add(element)
- @ary.push(element) unless @ary.include?(element)
- end
- def remove(element)
- @ary.delete(element)
- end
- def top
- @ary.first
- end
- def each
- if block_given? then
- @ary.each { |o| yield o }
- else
- @ary.each
- end
- end
- def empty?
- @ary.empty?
- end
- end
- class GameWindow < Gosu::Window
- def initialize
- super(640, 480, false)
- self.caption = "I made a terrible shooter game!"
- @player = Player.instance
- @player.giveWindow(self)
- end
- def update
- [Gosu::KbLeft, Gosu::KbUp, Gosu::KbRight, Gosu::KbDown, Gosu::KbSpace].each do |btn|
- if button_down?(btn)
- $actionQueue.add(btn)
- else
- $actionQueue.remove(btn)
- end
- end
- $gameTicks += 1
- nextEnemy = $fatedEnemyQueue.top
- unless nextEnemy.nil?
- if nextEnemy[0] < $gameTicks then
- $liveEnemyQueue.add(nextEnemy[1])
- $fatedEnemyQueue.remove(nextEnemy)
- end
- end
- @player.update
- $bulletQueue.each { |bullet| bullet.update }
- $liveEnemyQueue.each { |enemy| enemy.update }
- end
- def draw
- Gosu::Image.new(self, "./map.png", false, 0,
- MAP_HEIGHT - SCREEN_HEIGHT - [$gameTicks, 3358].min,
- SCREEN_WIDTH, SCREEN_HEIGHT).draw(0, 0, 0)
- @player.draw
- $bulletQueue.each { |bullet| bullet.draw }
- $liveEnemyQueue.each { |enemy| enemy.draw }
- end
- end
- # Most global variables will be here
- $gameTicks = 0
- $actionQueue = SetQueue.new
- $bulletQueue = SetQueue.new
- $liveEnemyQueue = SetQueue.new
- $deadEnemyQueue = SetQueue.new
- # List of enemies
- class TinyShip < Enemy
- def initialize(window, x, y)
- @window = window
- super(Gosu::Image.new(window, "./TinyShip.png", false, 0, 0, 23, 27),
- Box.new(x, y, 23, 27), Box.new(x + 3, y, 17, 24), 1)
- end
- def fireBullet
- # Target hitbox, center-x and center-y
- thb = Player.instance.hitbox
- tcx = ((thb.x + thb.w) / 2.0).to_i
- tcy = ((thb.y + thb.h) / 2.0).to_i
- # center-x and center-y for self
- cx = ((@hitbox.x + @hitbox.w) / 2.0).to_i
- cy = ((@hitbox.y + @hitbox.h) / 2.0).to_i
- # Distance between central points
- dst = Math.sqrt(((tcx - cx) ** 2) + ((tcy - cy) ** 2))
- vx = (10 * (tcx - cx) / dst).to_i
- vy = (10 * (tcy - cy) / dst).to_i
- img = Gosu::Image.new(@window, "./TinyShipBullet.png", false, 0, 0, 9, 9)
- box = Box.new(@drawbox.x + 5, @drawbox.y + 9, 9, 9)
- $bulletQueue.add(Bullet.new(img, box, box, vx, vy))
- end
- def update
- if ($gameTicks % 5).zero? then
- fireBullet
- end
- end
- end
- gamewindow = GameWindow.new
- $fatedEnemyQueue = SetQueue.new(
- [30, TinyShip.new(gamewindow, 250, 300)]
- )
- gamewindow.show
Advertisement
Add Comment
Please, Sign In to add comment