I want to loop the background image in a Ruby gosu side scrolling game.I have problem with the counters #k and #p which are used to translate the background image and the duplicated background image.I can't think of a good way to plus them.Here's the code to make it more clear.
require 'gosu'
class GameWindow < Gosu::Window
attr_accessor :x, :y
SHIFT = 700
def initialize
super 640,440
#background = Gosu::Image.new("./images/bg.png")
#player = Gosu::Image.new("./images/000.png")
#x1, #y1 = 0, 0
#player_x, #player_y = 50, 50
#k = 0 #counter
#p = 1 #counter
end
def update
#x1 -= 3
#player_y += 1 if #player_y <= 375
#player_y -= 3 if button_down?(Gosu::KbSpace) and #player_y >= 0
#coordinates = Gosu::Image.from_text(
self, "#{#x1},#{#k}", Gosu.default_font_name, 30)
#here should be the code for #k and #p
end
def draw
#background.draw(#x1 + #k*SHIFT, #y1, 0)
#background.draw(#x1 + #p*SHIFT, #y1, 0)
#player.draw(#player_x, #player_y, 0)
#coordinates.draw(0, 0, 1)
end
def button_down(id)
$window.close if id == Gosu::KbEscape
end
end
window = GameWindow.new
window.show
So how do I plus the counters #k and #p.Tried this
if #x1 > -(SHIFT+5)*#p and #x1 < -SHIFT*#p #705 and 700
#k += 2
end
if #k > 0 and #x1 > -SHIFT*#k - 5 and #x1 < -SHIFT*#k - 3 #1405 and 1403
#p += 2
end
but it works only in the beginning(2-3 image shifts).
Also tried this
if #x1 == -SHIFT*#p
#k += 2
end
and it did not work.
Assumptions & Analysis
I'm assuming that #x1 and #y1 are the offset of the left-most background's origin relative to the screen. You also have #player_x and #player_y, which denote the player's position on the screen. Your player is kept horizontally in the center of the screen, moving vertically when jumping or falling, and your background only scrolls horizontally. Also, your window size is 640x440, and your background image is 700px wide and at least 440px tall.
Your update step handles things like jumping (but only to the top of the screen) and simple, constant-velocity gravity, both by modifying the player's screen coordinates. It also provides a constant horizontal scroll by subtracting 3 units per frame from #x1, the horizontal camera coordinate in world-space.
Your draw step then takes the background image and draws two of them, offsetting them by the camera offset #x1 plus the shift for which image is which. This shift, however, isn't important, because we can calculate it fairly simply, and we're left without having to manipulate the additional state of remembering which one is which.
Code Solution
Instead of remembering the counter values #k and #p, we're going to just modulo by the image width to eliminate the excess in #x1 and get it where we need it to be. #k and #p are useless, so delete those. SHIFT can be deleted as well.
Instead, we need to do the following:
Calculate the screen offset of the left-most image
Determine if two images need to be drawn instead of just one
Draw the image(s)
Our goal looks something like this:
Our new code:
require 'gosu'
class GameWindow < Gosu::Window
attr_accessor :x, :y # side note - are these used at all?
def initialize
super 640,440
#background = Gosu::Image.new("./images/bg.png")
#player = Gosu::Image.new("./images/000.png")
#x1, #y1 = 0, 0
#player_x, #player_y = 50, 50
end
def update
#x1 -= 3
#player_y += 1 if #player_y <= 375
#player_y -= 3 if button_down?(Gosu::KbSpace) and #player_y >= 0
#coordinates = Gosu::Image.from_text(
self, "#{#x1}", Gosu.default_font_name, 30)
end
def draw
# the important bits!
#local_x = #x1 % -#background.width
#background.draw(#local_x, #y1, 0)
#background.draw(#local_x + #background.width, #y1, 0) if #local_x < (#background.width - self.width)
#player.draw(#player_x, #player_y, 0)
#coordinates.draw(0, 0, 1)
end
def button_down(id) # Side note: Does this work correctly?
$window.close if id == Gosu::KbEscape
end
end
window = GameWindow.new
window.show
What this does is, it takes #x1 and modulos it with the (negative) width of the image. This means, it gives the remainder of an integer division of the two numbers. Modulo is very useful when trying to ensure that an integer value "wraps around" after exceeding a limit in either direction; it effectively maps it to between 0 (inclusive) and the given number (exclusive). The results of this are illustrated in the graphic above.
Keep in mind this solution only works as-is for your circumstances; vertical scrolling will require more code, and this will break pretty quickly if your window becomes wider than your background image.
Related
I'm using Gosu (with Ruby version 2.5.5) to create my first game. I created a map and I want to implement scrolling with the cursor. Using Gosu example "Cptn Ruby" as a guide, I was successful to a point.
Here is what I have so far.
def update
# map.width is the number of background tiles in each row and map.height is the number of tiles in
# each column. Each tile is 14x14 pixels.
#camera_x = [[self.mouse_x - (WIDTH / 2), 0].max, #map.width * 14 - WIDTH].min
#camera_y = [[self.mouse_y - (HEIGHT / 2), 0].max, #map.height * 14 - HEIGHT].min
end
def draw
#cursor.draw(self.mouse_x, self.mouse_y, 100, scale_x = 0.65, scale_y = 0.65)
Gosu.translate(-#camera_x, -#camera_y) {#map.draw}
end
This does scroll, but only to maximum point. Once the cursor reaches the bottom of the screen, the camera_y value will not get greater than 239 (and the same problem goes for camera_x). I can increase the scrolling distance by multiplying the value by 2 like so:
#camera_y = [[(self.mouse_y - (HEIGHT / 2) * 2, 0].max, #map.height * 14 - HEIGHT].min
However, while I scroll further with this approach, it still stops. I would like to continuously scroll while the mouse is at the bottom (or side) of the screen. I am confused as to why it is not doing this already since gosu::update runs 60 times per second. I would have thought that every time it runs it would add to my #camera_y and/or #camera_x variables if the cursor is in the right spot, but that is not happening.
I've also tried this:
if self.mouse_y > (HEIGHT * 0.67) # if mouse is in lower 3rd of screen
#camera_y += 10
end
This simply moves scrolls 10 pixels once instead of continuously.
I could do this easily with a loop, but I found that loops in Gosu update or draw cause the program to crash.
Any thoughts?
I figured out a way to do it.
def initialize
super WIDTH, HEIGHT, :fullscreen => false
self.caption = "Ecosystem Beta"
#map = Map.new("c:/users/12035/.atom/Ecosystem/eco_map.txt", :tileable => true)
#cursor = Gosu::Image.new("c:/users/12035/.atom/media/cursor.png")
#camera_x = 0
#camera_y = 0
end
def update
if self.mouse_y > (HEIGHT * 0.67) # if mouse is in lower 3rd of screen
#camera_y = [#camera_y += 10, 740].min # scrolls to a max of 740 pixels
elsif self.mouse_y < (HEIGHT * 0.33)
#camera_y = [#camera_y -= 10, 0].max # will not scroll past the begginining of the map
end
end
I'm trying to test out game mechanics by writing lua code in the LÖVE game development environment. My intent is to have enemy units spawn in one of five lanes and travel across the screen. At the moment, I am able to generate said units and constrain them to the boundaries of where the "lanes" would be. However, I am not sure how to have them spawn explicitly in one of five lanes.
Right now, I am using math.random() to choose a number between the top and bottom boundaries of the lanes. Enemy unit sprites are 32x32, so five discrete lanes will cover a total of 160 pixels of the screen. Right now, I have the boundaries set to 240 and 400 (middle of the screen's height). How do I generate a random number in increments of 32 such that enemies will spawn in one of five possible lanes?
EDIT: So, it seems that the boundaries are not actually set to the middle. Will be trying to figure out the coordinates on that, but if someone can toss me a freebie tip on that I'd be happy! :D
My current working code is as follows:
-- Axis-based collision detection. Effective for rectangular units; does not work well with circles and non-quad shapes.
function CheckCollision(x1,y1,w1,h1, x2,y2,w2,h2)
return x1 < x2+w2 and
x2 < x1+w1 and
y1 < y2+h2 and
y2 < y1+h1
end
debug = true
-- Timers declared here to reference later.
createEnemyTimerMax = 0.4
createEnemyTimer = createEnemyTimerMax
-- Delcares images; will actually be established in love.load and brought out in love.draw
enemyImg = nil
enemies = {}
function love.load(arg)
enemyImg = love.graphics.newImage('assets/cat.png')
end
function love.update(dt)
if love.keyboard.isDown('escape') then
love.event.push('quit')
end
--Generates enemies.
--Create 5 if/then conditions to account for each lane?
--Conditions dependent on createEnemyTimer being < 0 and # of enemies in lane < 5
--First condition is met; how do we create the second?
--Will each lane need its own timer?
createEnemyTimer = createEnemyTimer - (.5 * dt)
if createEnemyTimer < 0 then
createEnemyTimer = createEnemyTimerMax
-- Create an enemy
randomNumber = math.random(240,400)
newEnemy = { y = randomNumber, x = 650, img = enemyImg }
table.insert(enemies, newEnemy)
end
--Updates position of enemies.
for i, enemy in ipairs(enemies) do
enemy.x = enemy.x - (200 * dt)
if enemy.x < 0 then -- remove enemies when they pass off the screen
table.remove(enemies, i)
end
end
-- Collision detection.
-- For MSJ, collision should prevent PC from moving onto enemy's square.
--[[
for i, enemy in ipairs(enemies) do
for j, bullet in ipairs(bullets) do
if CheckCollision(enemy.x, enemy.y, enemy.img:getWidth(), enemy.img:getHeight(), bullet.x, bullet.y, bullet.img:getWidth(), bullet.img:getHeight()) then
table.remove(bullets, j)
table.remove(enemies, i)
score = score + 1
end
end
if CheckCollision(enemy.x, enemy.y, enemy.img:getWidth(), enemy.img:getHeight(), player.x, player.y, player.img:getWidth(), player.img:getHeight())
and isAlive then
table.remove(enemies, i)
isAlive = false
end
end
]]
end
function love.draw(dt)
for i, enemy in ipairs(enemies) do
love.graphics.draw(enemy.img, enemy.x, enemy.y)
end
end
I think you should change the approach on how to generate the five enemies, each on a lane. What about generating 5 random numbers that mark the relative position of each lane? Then you add this relative position to the beginning of each lane. For instance:
local enemies = {}
local origin_y, height = 240, 32
for i=1,5 do
local relative_y = math.random(32)
local y = origin_y + height * i + relative_y
table.insert(enemies, { x = math.random(), y = y})
end
I'm following the book "Learn game programming with ruby"
one of the exercises is loading an image with gosu and making it bounce off the edges of the screen. I followed the exercise and the image bounces fine off the top and left corners but will sink past the edge of the screen for awhile before bouncing off the bottom and right sides.
require 'gosu'
class Window < Gosu::Window
def initialize
super(800, 600)
self.caption = 'First Game'
#blueheart = Gosu::Image.new('blueheart.png')
#x = 200
#y = 200
#width = 50
#height = 43
#velocity_x = 2
#velocity_y = 2
#direction = 1
end
def update
#x += #velocity_x
#y += #velocity_y
#velocity_x*= -1 if #x + #width /2 > 800 || #x - #width / 2 < 0
#velocity_y*= -1 if #y + #height /2 > 600 || #y - #height / 2 < 0
end
def draw
#blueheart.draw(#x - #width/2, #y - #height/2, 1)
end
end
window = Window.new
window.show
I think it has something to do with how ruby uses the top right corner of the image as the coordinates for an image but I thought
#blueheart.draw(#x - #width/2, #y - #height/2, 1)
was supposed to fix that, How can I make it work like I want?
Thanks in advance
The Problem came from me creating my own sprites and not realizing the height and width values would be different.
changing the code to #width = #blueheart.width gave me a crash
but I just changed the values to the proper width and height and fixed the problem. the values #width = 50 and #height = 43 were referring to different sprite sizes in the book.
I'm trying to code a program that can take text and animate it to bounce on a loop, like a ball bouncing to the floor. I used a similar piece of code I found a starting point as I'm still fairly new to Pygame (thank you Pete Shinners, whoever you are), but after updating the code and playing with it for a long time I still can't get it to blit to the screen correctly. The text starts above the rendered area and then gradually falls into view, but the top part of the text is cut off.
I've tried moving the blitted region around the window and resizing the rectangles and surface the program is using, but nothing seems to fix it.
import os, sys, math, pygame, pygame.font, pygame.image
from pygame.locals import *
def bounce():
# define constants
G = 0.98
FLOOR = 0
COEFFICIENT = 0.8
#define variables
ball = 500
direction = 'DOWN'
v = 0
count = 0
#create array to store data
array = [ball]
while True:
if count == 4:
return array
elif ball > FLOOR and direction == 'DOWN':
v += G
if (ball - v) >= FLOOR:
ball = ball - v
array.append(round(ball,2))
else:
ball = FLOOR
array.append(round(ball,2))
direction = 'UP'
v *= COEFFICIENT
count += 1
elif ball >= FLOOR and direction == 'UP':
v -= G
if (ball + v) >= FLOOR:
ball = ball + v
array.append(round(ball,2))
if v <= 0:
direction = 'DOWN'
else:
ball = FLOOR
array.append(ball)
direction = 'UP'
v *= COEFFICIENT
class textBouncy:
array = bounce()
def __init__(self, font, message, fontcolor, amount=10):
# Render the font message
self.base = font.render(message, 0, fontcolor)
# bounce amount (height)
self.amount = amount
#size = rect of maximum height/width of text
self.size = self.base.get_rect().inflate(0, amount).size
#normalise array to meet height restriction
self.array = [round(-x/(500/amount),2) for x in array]
def animate(self):
# create window surface s
s = pygame.Surface(self.size)
# height = max inflated height
height = self.size[1]
# define a step-sized rectangle in the location of the step
src = Rect(0, 0, self.base.get_width(), height)
# moves the message according to the array list.
dst = src.move(0, self.array[i])
if (i + 1) == len(self.array):
global i
i = 0
# blits the information onto the screen
s.blit(self.base, dst, src)
return s
entry_info = 'Bouncing ball text'
if __name__ == '__main__':
pygame.init()
#create text renderer
i = 0
array = bounce()
bigfont = pygame.font.Font(None, 60)
white = 255, 255, 255
renderer = textBouncy(bigfont, entry_info, white, 16)
text = renderer.animate()
#create a window the correct size
win = pygame.display.set_mode(text.get_size())
win.blit(text, (0, 10))
pygame.display.flip()
#run animation loop
finished = 0
while True:
pygame.time.delay(10)
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
text = renderer.animate()
i += 1
win.blit(text, (0, 10)) # blits the finished product from animate
pygame.display.flip()
(Quote) "it all comes down to math really" Kay so you need to - the y axis when you want to make it go up and + the x axis to make it go side ways you could make it go up and down will moveing it horizontally and then when it reaches a point it will stop moving horizontally and just bonce up and down +ing it more every time
That was my 100$ which took me 5 mins to write
After revisiting this I managed to work this out - I needed to add everything I blitted down to compensate for the bounce up. So in the __init__function:
self.array = [round(-x/(500/amount),2)**+self.amount** for x in array]
Works perfectly now :)
I have been tinkering with making a drawing application in Gosu using Ruby, but am running into some high CPU usage. I know one possible cause.
First, I am simply adding a primitive to an hash every time the user clicks the mouse button (or if they hold down the mouse button, every time the screen refreshes, which I think happen around 60 times per second). Each time the screen redraws I have the program loop through the hash and redraw all the primitives. If someone holds down their mouse button it will just keep overlapping primitives on top of each other and in other situations the primitives just layer. I can see how that would eventually eat up a bunch of memory in the hash.
I feel that the best solution would be to use some sort of lower level function that simply colors pixels on the screen and doesn't have to store them in an hash. I'm not sure if Gosu supports that. Could someone either help me with this in Gosu or suggest a different method and/or another program to use? Thank you!
(Below is the complete code for my program.)
require 'Gosu'
require 'socket'
class GameWindow < Gosu::Window
def initialize
super 1280, 960, false
self.caption = "Drawz Server"
#background_image = Gosu::Image.new(self, "media/Space.png", true)
#player = Player.new(self)
# Platform is the blank image for the epicycle to be drawn on.
#earth_image = Gosu::Image.new(self, "media/earth.bmp", true)
#cursor_image = Gosu::Image.new(self, "media/cursor.bmp", true)
puts "Please enter port..."
port = gets.strip!
puts "Server is running."
# Initialized Instance variables for all methods.
#square_drawing_number = 0
#client_coordinates_array = []
#client_coordinates_array_array = []
#client_quad_hash = {}
server = TCPServer.open("10.64.8.2", port)
#client = server.accept
manage_client_data = Thread.new do
x = 0
loop do
#client_coordinates_array[x] = #client.gets
x += 1
end
end
#x = 0
#quad_hash = {}
# Start up value of things in draw method, because draw is called before update. Hence I put the start up values here so that when draw is called it has location of things in
# its arguments.
#cx = 0
#cy = 0
end
class QuadCoords
attr_reader :x1, :y1, :x2, :y2, :x3, :y3, :x4, :y4
def initialize(x1, y1, x2, y2, x3, y3, x4, y4)
#x1 = x1
#y1 = y1
#x2 = x2
#y2 = y2
#x3 = x3
#y3 = y3
#x4 = x4
#y4 = y4
end
end
# Mean't to change location (x and y) and such of things in draw method.
def update
#cx = mouse_x
#cy = mouse_y
#x += 1
# Adds square locations to hash which is then accessed by draw function to retrieve and draw squares.
if button_down? Gosu::MsLeft
#x += 1
quad_coords_obj = QuadCoords.new(#cx - 5, #cy - 5, #cx, #cy - 5, #cx - 5, #cy, #cx, #cy)
#quad_hash["quad#{#x}"] = quad_coords_obj
# Sends location of squares to client for it to draw.
#client.puts "#{quad_coords_obj.x1}---#{quad_coords_obj.y1}---#{quad_coords_obj.x2}---#{quad_coords_obj.y2}---#{quad_coords_obj.x3}---#{quad_coords_obj.y3}---#{quad_coords_obj.x4}---#{quad_coords_obj.y4}"
end
if #client_coordinates_array.length > #square_drawing_number
new_squares_to_add = #client_coordinates_array.length - #square_drawing_number
#client_coordinates_array[-1 * new_squares_to_add..-1].each do |value|
#client_coordinates_array_array << value.split(/\---/)
end
x = 1
new_squares_to_add.times do
#x += 1
#client_quad_coords_obj = QuadCoords.new(#client_coordinates_array_array[-1 * x][0].to_i,
#client_coordinates_array_array[-1 * x][1].to_i,
#client_coordinates_array_array[-1 * x][2].to_i,
#client_coordinates_array_array[-1 * x][3].to_i,
#client_coordinates_array_array[-1 * x][4].to_i,
#client_coordinates_array_array[-1 * x][5].to_i,
#client_coordinates_array_array[-1 * x][6].to_i,
#client_coordinates_array_array[-1 * x][7].to_i)
#client_quad_hash["quad#{#x}"] = #client_quad_coords_obj
x += 1
#square_drawing_number += 1
end
end
end
# Draw is called before update.
def draw
#background_image.draw(0,0,0)
#earth_image.draw(295,215,1)
# x1,y1 = Upper Left Hand Corner; x2,y2 = Lower Left Hand Corner; x3,y3 = Upper Right Hand Corner; x4,y4 = Lower Right Hand Corner
#quad_hash.each_value do |value|
draw_quad(value.x1, value.y1, 0xff00ffff, value.x2, value.y2, 0xff00ffff, value.x3, value.y3, 0xff00ffff, value.x4, value.y4, 0xff00ffff, z = 0, mode = :default)
end
#client_quad_hash.each_value do |value|
draw_quad(value.x1, value.y1, 0xff00ff00, value.x2, value.y2, 0xff00ff00, value.x3, value.y3, 0xff00ff00, value.x4, value.y4, 0xff00ff00, z = 0, mode = :default)
end
#cursor_image.draw(#cx, #cy, 1)
end
def button_down(id)
if id == Gosu::KbEscape
close
end
end
end
class Player
attr_accessor :x, :y, :update_called
def initialize(window)
#image = Gosu::Image.new(window, "media/Sun.bmp", false)
end
end
window = GameWindow.new
window.show
You can use TexPlay for drawing over Gosu::Image and then render this image.
http://banisterfiend.wordpress.com/2008/08/23/texplay-an-image-manipulation-tool-for-ruby-and-gosu/
for example:
image1.paint {
circle 20,20,10, :color => :red
rect 40,40,50,50, :color => :green
pixel 60,60, :color => :blue
}
There are alternatives: RMagick (that's more powerful, but much more complex to install\distribute) and Ashton (it has methods of rendering to OpenGL texture, so it's faster, but requires some understanding of OpenGL).
I suggest using my Ashton gem to render directly to textures - Unlike TexPlay or other software rendering, this is hardware accelerated, so is very fast! Using Ashton::Texture#render, you can cache drawing operations into a texture as quickly as you can draw them onto the screen.
def initialize(...)
# WindowBuffer is just an Ashton::Texture that is the same size as the window
#draw_buffer = Ashton::WindowBuffer.new
...
end
def update()
#draw_buffer.render do
# Perform new gosu drawing actions (e.g. draw_quad)
# here to draw directly into the texture.
# Both the local ones and the ones pulled from the network.
end
end
def draw()
# Since you are drawing things in the order they should appear,
# you don't need to worry about Z values.
#background_image.draw 0, 0, 0
#earth_image.draw 295, 215, 0
#draw_buffer.draw 0, 0, 0
#cursor_image.draw #cx, #cy, 0
end
Also note that printing text is actually quite slow, so will affect your FPS if performed in every update/draw.