Skip to content

Instantly share code, notes, and snippets.

@jacius
Created July 29, 2009 00:48
Show Gist options
  • Save jacius/157785 to your computer and use it in GitHub Desktop.
Save jacius/157785 to your computer and use it in GitHub Desktop.
#!/bin/env ruby
# One way of making an object start and stop moving gradually:
# make user input affect acceleration, not velocity.
require "rubygame"
# Include these modules so we can type "Surface" instead of
# "Rubygame::Surface", etc. Purely for convenience/readability.
include Rubygame
include Rubygame::Events
include Rubygame::EventActions
include Rubygame::EventTriggers
# A class representing the player's ship moving in "space".
class Ship
include Sprites::Sprite
include EventHandler::HasEventHandler
def initialize( px, py )
@px, @py = px, py # Current Position
@vx, @vy = 0, 0 # Current Velocity
@ax, @ay = 0, 0 # Current Acceleration
@max_speed = 400.0 # Max speed on an axis
@accel = 1200.0 # Max Acceleration on an axis
@slowdown = 800.0 # Deceleration when not accelerating
@keys = [] # Keys being pressed
# The ship's appearance. A white square for demonstration.
@image = Surface.new([20,20])
@image.fill(:white)
@rect = @image.make_rect
# Create event hooks in the easiest way.
make_magic_hooks(
# Send keyboard events to #key_pressed() or #key_released().
KeyPressed => :key_pressed,
KeyReleased => :key_released,
# Send ClockTicked events to #update()
ClockTicked => :update
)
end
private
# Add it to the list of keys being pressed.
def key_pressed( event )
@keys += [event.key]
end
# Remove it from the list of keys being pressed.
def key_released( event )
@keys -= [event.key]
end
# Update the ship state. Called once per frame.
def update( event )
dt = event.seconds # Time since last update
update_accel
update_vel( dt )
update_pos( dt )
end
# Update the acceleration based on what keys are pressed.
def update_accel
x, y = 0,0
x -= 1 if @keys.include?( :left )
x += 1 if @keys.include?( :right )
y -= 1 if @keys.include?( :up ) # up is down in screen coordinates
y += 1 if @keys.include?( :down )
# Scale to the acceleration rate. This is a bit unrealistic, since
# it doesn't consider magnitude of x and y combined (diagonal).
x *= @accel
y *= @accel
@ax, @ay = x, y
end
# Update the velocity based on the acceleration and the time since
# last update.
def update_vel( dt )
@vx = update_vel_axis( @vx, @ax, dt )
@vy = update_vel_axis( @vy, @ay, dt )
end
# Calculate the velocity for one axis.
# v = current velocity on that axis (e.g. @vx)
# a = current acceleration on that axis (e.g. @ax)
#
# Returns what the new velocity (@vx) should be.
#
def update_vel_axis( v, a, dt )
# Apply slowdown if not accelerating.
if a == 0
if v > 0
v -= @slowdown * dt
v = 0 if v < 0
elsif v < 0
v += @slowdown * dt
v = 0 if v > 0
end
end
# Apply acceleration
v += a * dt
# Clamp speed so it doesn't go too fast.
v = @max_speed if v > @max_speed
v = -@max_speed if v < -@max_speed
return v
end
# Update the position based on the velocity and the time since last
# update.
def update_pos( dt )
@px += @vx * dt
@py += @vy * dt
@rect.center = [@px, @py]
end
end
# The Game class helps organize thing. It takes events
# from the queue and handles them, sometimes performing
# its own action (e.g. Escape key = quit), but also
# passing the events to the pandas to handle.
#
class Game
include EventHandler::HasEventHandler
def initialize()
make_screen
make_clock
make_queue
make_event_hooks
make_ship
end
# The "main loop". Repeat the #step method
# over and over and over until the user quits.
def go
catch(:quit) do
loop do
step
end
end
end
private
# Create a new Clock to manage the game framerate
# so it doesn't use 100% of the CPU
def make_clock
@clock = Clock.new()
@clock.target_framerate = 50
@clock.calibrate
@clock.enable_tick_events
end
# Set up the event hooks to perform actions in
# response to certain events.
def make_event_hooks
hooks = {
:escape => :quit,
:q => :quit,
QuitRequested => :quit
}
make_magic_hooks( hooks )
end
# Create an EventQueue to take events from the keyboard, etc.
# The events are taken from the queue and passed to objects
# as part of the main loop.
def make_queue
# Create EventQueue with new-style events (added in Rubygame 2.4)
@queue = EventQueue.new()
@queue.enable_new_style_events
# Don't care about mouse movement, so let's ignore it.
@queue.ignore = [MouseMoved]
end
# Create the Rubygame window.
def make_screen
@screen = Screen.open( [640, 480] )
@screen.title = "Square! In! Space!"
end
# Create the player ship in the middle of the screen
def make_ship
@ship = Ship.new( @screen.w/2, @screen.h/2 )
# Make event hook to pass all events to @ship#handle().
make_magic_hooks_for( @ship, { YesTrigger.new() => :handle } )
end
# Quit the game
def quit
puts "Quitting!"
throw :quit
end
# Do everything needed for one frame.
def step
# Clear the screen.
@screen.fill( :black )
# Fetch input events, etc. from SDL, and add them to the queue.
@queue.fetch_sdl_events
# Tick the clock and add the TickEvent to the queue.
@queue << @clock.tick
# Process all the events on the queue.
@queue.each do |event|
handle( event )
end
# Draw the ship in its new position.
@ship.draw( @screen )
# Refresh the screen.
@screen.update()
end
end
# Start the main game loop. It will repeat forever
# until the user quits the game!
Game.new.go
# Make sure everything is cleaned up properly.
Rubygame.quit()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment