Welcome!
This is the community forum for my apps Pythonista and Editorial.
For individual support questions, you can also send an email. If you have a very short question or just want to say hello — I'm @olemoritz on Twitter.
Cloud Jump Game
-
https://gist.github.com/9098744
I've been wanting to do something with the accelerometer for a while, and then I saw this game on the Codea demo movie.
I've always though doodle jump was a cool game, so here it is on Pythonista.
It would be cool if someone made the clouds look better, though.
-
E.g. you could resize the built-in 'Cloud'!?
-
I tried to make the clouds as it was made on the Codea game:
from scene import * import random IMAGE_WIDTH = 101 IMAGE_HEIGHT = 171 IMAGE_Y_OFFSET = -30 BLOCK_HEIGHT = 40 BLOCK_DEPTH = 80 DEAD_ZONE_MIN = -0.02 DEAD_ZONE_MAX = 0.02 PLAYER_CONTROL_SPEED = 2000 PLAYER_BOUNCE_VELOCITY = 1700 PLAYER_INITIAL_BOUNCE = 1700 MAX_CLOUD_DIST = 500 DIFFICULTY_Q = 100000.0 GAME_GRAVITY = 2000 GAME_WAITING = 0 GAME_PLAYING = 1 GAME_DEAD = 2 class Player(object): def __init__(self): self.bounds = Rect() self.velocity = 0 def draw(self): tint(1,1,1) image('PC_Character_Pink_Girl', self.bounds.x, self.bounds.y + IMAGE_Y_OFFSET) class GroundBlock(object): def __init__(self): self.bounds = Rect() def draw(self): tint(1,1,1) image('PC_Grass_Block', self.bounds.x, self.bounds.y) class Cloud (object): def __init__(self): self.shapes = [] num_circles = random.randint(4, 5) for i in xrange(num_circles): x = i * 20 - ((num_circles/2)*30) y = (random.random()-0.5) * 30 rad = random.randint(50, 100) self.shapes.append([x, y, rad]) self.width = num_circles * 30 + 30 self.bounds = Rect(0, 0, self.width, 60) def is_colliding(self, pos): startp = self.bounds.x - self.width/2 endp = self.bounds.x + self.width/2 if ((pos.x < endp) and (pos.x > startp) and (pos.y < (self.bounds.y + 30)) and (pos.y > (self.bounds.y + 10))): return True return False def draw(self): push_matrix() translate(self.bounds.x, self.bounds.y) no_stroke() fill(0.90, 0.90, 0.90) for i in self.shapes: ellipse(i[0], i[1] - 5, i[2], i[2]) fill(1.00, 1.00, 1.00) for i in self.shapes: ellipse(i[0], i[1] + 5, i[2], i[2]) pop_matrix() class MyScene (Scene): def create_ground(self): for x in range((int(self.bounds.w) / IMAGE_WIDTH) + 1): block = GroundBlock() block.bounds = Rect(x * IMAGE_WIDTH, 0, IMAGE_WIDTH, IMAGE_HEIGHT) self.scenery.append(block) def generate_clouds(self): y = self.cloud_height while self.cloud_height < self.bounds.h * 2: q = min(self.climb, DIFFICULTY_Q) min_dist = int(MAX_CLOUD_DIST * q / DIFFICULTY_Q) max_dist = int(MAX_CLOUD_DIST / 2 + min_dist / 2) self.cloud_height += random.randint(min_dist, max_dist) cloud = Cloud() cloud.bounds.x = random.random() * (self.bounds.w - 150) cloud.bounds.y = self.cloud_height self.scenery.append(cloud) def cull_scenery(self): i = len(self.scenery) for sprite in self.scenery[:]: if sprite.bounds.top() < 0: self.scenery.remove(sprite) def control_player(self): tilt = gravity().x if(tilt < DEAD_ZONE_MIN) or (tilt > DEAD_ZONE_MAX): move = self.dt * tilt * PLAYER_CONTROL_SPEED self.player.bounds.x += move if(self.player.bounds.x < 0): self.player.bounds.x = 0 elif(self.player.bounds.x > self.bounds.w - self.player.bounds.w): self.player.bounds.x = self.bounds.w - self.player.bounds.w def lower_scenery(self, y): self.climb += y self.cloud_height -= y for sprite in self.scenery: sprite.bounds.y -= y def run_gravity(self): player_y_move = self.dt * self.player.velocity scenery_y_move = 0 old_velocity = self.player.velocity self.player.velocity -= self.dt * GAME_GRAVITY if(old_velocity > 0) and (self.player.velocity <= 0): self.player_apex_frame = True self.player.bounds.y += player_y_move if(self.player.bounds.y >= self.player_max_y): scenery_y_move = self.player.bounds.y - self.player_max_y self.player.bounds.y = self.player_max_y self.lower_scenery(scenery_y_move) elif(self.player.bounds.top() < 0): self.game_state = GAME_DEAD def collision_detect(self): bounce = False if(self.player.velocity < 0): p = Point(self.player.bounds.x + self.player.bounds.w/2, self.player.bounds.y) for sprite in self.scenery: if hasattr(sprite, 'is_colliding'): collision = sprite.is_colliding(p) else: collision = p in sprite.bounds if collision: self.player.velocity = PLAYER_BOUNCE_VELOCITY break def game_loop(self): if self.game_state == GAME_PLAYING: self.run_gravity() self.collision_detect() self.control_player() if self.player_apex_frame: self.cull_scenery() self.generate_clouds() self.player_apex_frame = False def shadow_text(self, s, x, y): tint(0,0,0) text(s, 'AvenirNext-Heavy', 48, x + 2, y - 2) tint(0.00, 0.50, 1.00) text(s, 'AvenirNext-Heavy', 48, x, y) def draw_text(self): if(self.game_state == GAME_WAITING): if(int(self.t) % 2): self.shadow_text('Tap Screen to Start', self.bounds.w / 2, self.bounds.h * 0.6) self.shadow_text('Tilt Screen to Steer', self.bounds.w / 2, self.bounds.h * 0.4) elif(self.game_state == GAME_PLAYING): self.shadow_text('Score : ' + str(int(self.climb / 10)), self.bounds.w / 2, self.bounds.h * 0.95) if(self.game_state == GAME_DEAD): self.shadow_text('Score : ' + str(int(self.climb / 10)), self.bounds.w / 2, self.bounds.h * 0.95) self.shadow_text('Game Over', self.bounds.w / 2, self.bounds.h / 2) def setup(self): self.game_state = GAME_WAITING self.scenery = [] self.climb = 0 self.create_ground() self.cloud_height = 200 self.generate_clouds() self.player = Player() self.player_apex_frame = False self.player.bounds = Rect(self.bounds.w / 2 - IMAGE_WIDTH / 2, BLOCK_HEIGHT + BLOCK_DEPTH / 2, IMAGE_WIDTH, IMAGE_HEIGHT) self.player_max_y = self.bounds.h * 0.6 def draw(self): self.game_loop() background(0.40, 0.80, 1.00) for sprite in self.scenery: sprite.draw() self.player.draw() self.draw_text() def touch_began(self, touch): if self.game_state == GAME_WAITING: self.game_state = GAME_PLAYING self.player.velocity = PLAYER_INITIAL_BOUNCE elif self.game_state == GAME_DEAD: self.setup() run(MyScene(), PORTRAIT)
-
Thanks Sebastian - that looks fantastic. I have updated the Gist with your clouds.
-
@bashedcrab
I am really enjoying your games you so awesome...
-
@bashedcrab Nice! :D
-
I added some sounds to the game. https://github.com/tjferry14/Cloud-Jump-2
-
I could use some PIL graphics assistance... The clouds in the app are beautiful but when I tried to port them to PIL they look ugly. :-( Would it be possible for someone to make the PIL image-based clouds look as beautiful as in the app?
-
Maybe @Sebastian can help
-
@ccc: Not perfect, but I think you ...
import Image, ImageDraw, random, scene def generate_shapes(num_circles): shapes = [] for i in xrange(num_circles): x = i * 20 - ((num_circles/2)*30) #range: -74 .. +40 y = (random.random()-0.5) * 30 #range: -15 .. +15 x += 75 # this is a hack! y += 20 # this is a hack! rad = random.randint(50, 100) #range: +50 .. +100 shapes.append([x, y, rad]) return shapes def cloud_maker(): num_circles = 5 #range: 4..5 => I prefer 5 :) #image_size = ((num_circles + 1) * 30, 60) image_size = (214, 140) theImage = Image.new('RGBA', image_size) #, 'pink') draw = ImageDraw.Draw(theImage) circles = generate_shapes(num_circles) for i in circles: bbox = (i[1] - 5, i[0], i[2], i[2]) # x/y swap draw.ellipse(bbox, fill='rgb(90%,90%,90%)') for i in circles: bbox = (i[1] + 5, i[0], i[2], i[2]) # x/y swap draw.ellipse(bbox, fill='white') del draw return theImage class Cloud(scene.Layer): def __init__(self, parent = None): cloud_image = cloud_maker() super(self.__class__, self).__init__(scene.Rect(*cloud_image.getbbox())) if parent: parent.add_layer(self) self.image = scene.load_pil_image(cloud_image) class MyScene(scene.Scene): def __init__(self): scene.run(self) def setup(self): self.cloud = Cloud(self) self.cloud.frame.x = self.bounds.w * 0.8 # x/y swap self.cloud.frame.y = 0 def draw(self): scene.background(0.40, 0.80, 1.00) self.root_layer.update(self.dt) self.root_layer.draw() self.cloud.frame.y -= 1 # x/y swap if not self.bounds.intersects(self.cloud.frame): del self.cloud # whack the old cloud self.setup() # and create a new one MyScene()
-
@brumm. Thanks for this but the PIL-based clouds are still not as beautiful as the ones in the game...
Three key attributes of the game clouds are missing:
- The long axis is generally the y axis instead if the x axis (this makes clouds more inviting to land on)
- The grey (silver!) lining is on the underside of the clouds (this gives them shading, depth, and believability)
- They are not as big and beautiful and believable as the original clouds.
My hacks at this problem have been fruitless.
-
I think I'm getting there! The ImageDraw.ImageDraw.ellipse function doesn't use a width and height value in its bounding box, but rather x and y values. So maybe something like this?
import Image, ImageDraw, random, scene def generate_shapes(num_circles): shapes = [] for i in xrange(num_circles): x = (i * 20 - ((num_circles/2)*30))+90 y = ((random.random()-0.5) * 30)+15 rad = random.randint(50, 100) shapes.append([x, y, rad]) return shapes def cloud_maker(): num_circles = random.randint(5, 6) image_size = (220, 140) theImage = Image.new('RGBA', image_size) draw = ImageDraw.Draw(theImage) circles = generate_shapes(num_circles) for i in circles: r = i[2] bbox = (i[0], 40-i[1], i[0]+r, 40-i[1]+r) draw.ellipse(bbox, fill='rgb(90%,90%,90%)') for i in circles: r = i[2] bbox = (i[0], 40-i[1]-10, i[0]+r, 40-i[1]+r-10) draw.ellipse(bbox, fill='white') del draw return theImage class Cloud(scene.Layer): def __init__(self, parent = None): cloud_image = cloud_maker() super(self.__class__, self).__init__(scene.Rect(*cloud_image.getbbox())) if parent: parent.add_layer(self) self.image = scene.load_pil_image(cloud_image) class MyScene(scene.Scene): def setup(self): self.cloud = Cloud(self) self.cloud.frame.x = self.bounds.w * 0.5 self.cloud.frame.y = self.bounds.h*0.8 def draw(self): scene.background(0.40, 0.80, 1.00) self.root_layer.update(self.dt) self.root_layer.draw() scene.rect(*self.cloud.frame) def touch_began(self, touch): self.root_layer.remove_layer(self.cloud) self.cloud = Cloud(self) self.cloud.frame.x = self.bounds.w * 0.5 self.cloud.frame.y = self.bounds.h*0.8 scene.run(MyScene())
-
PIL coordinate system is also flipped in y, so you need to use image height minus y.
Scene allowed you to draw outside of the frame, PIL does not. So you need to add 60/15 respectively so the cords are Zero based. Image size should be 200 wide (5th cloud starts at 80, and is 100 wide), and 140 tall (0 to 30 random height, plus 10 for shadow offset).
Then top would beh-i[1]
, bottom would beh-i[1]-i[2]
, and you would subtract 10 for both to shift gray cloud down. -
@JonB Thanks! I edited my post above. It seems to work ok now ;)
-
You guys are awesome. Thanks much.
-
OK guys... One issue remains :-(
The image frame is larger than the cloud itself. See: https://github.com/tjferry14/Cloud-Jump-2/issues/37
-
I found a solution that may work. @ccc What do you think?
import Image, ImageDraw, random, scene import numpy as np class Cloud(scene.Layer): def __init__(self, parent = None): cloud_image = self.create_image() super(self.__class__, self).__init__(scene.Rect(*cloud_image.getbbox())) if parent: parent.add_layer(self) self.image = scene.load_pil_image(cloud_image) def generate_shapes(self, num_circles): shapes = [] for i in xrange(num_circles): x = (i * 20 - ((num_circles/2)*30))+90 y = ((random.random()-0.5) * 30)+15 rad = random.randint(50, 100) shapes.append([x, y, rad]) return shapes # found on 'http://stackoverflow.com/questions/14211340/automatically-cropping-an-image-with-python-pil' def crop_image(self, img): image_data = np.asarray(img) image_data_bw = image_data.max(axis=2) non_empty_columns = np.where(image_data_bw.max(axis=0)>0)[0] non_empty_rows = np.where(image_data_bw.max(axis=1)>0)[0] cropBox = (min(non_empty_rows), max(non_empty_rows), min(non_empty_columns), max(non_empty_columns)) image_data_new = image_data[cropBox[0]:cropBox[1]+1, cropBox[2]:cropBox[3]+1, :] img = Image.fromarray(image_data_new) return img def create_image(self): num_circles = random.randint(5, 6) image_size = (220, 140) theImage = Image.new('RGBA', image_size) draw = ImageDraw.Draw(theImage) circles = self.generate_shapes(num_circles) for i in circles: r = i[2] bbox = (i[0], 40-i[1], i[0]+r, 40-i[1]+r) draw.ellipse(bbox, fill='rgb(90%,90%,90%)') for i in circles: r = i[2] bbox = (i[0], 40-i[1]-10, i[0]+r, 40-i[1]+r-10) draw.ellipse(bbox, fill='white') del draw return self.crop_image(theImage) class MyScene(scene.Scene): def setup(self): self.cloud = Cloud(self) self.cloud.frame.x = self.bounds.w * 0.5 self.cloud.frame.y = self.bounds.h*0.8 def draw(self): scene.background(0.40, 0.80, 1.00) scene.fill(0,0,0) scene.rect(*self.cloud.frame) self.root_layer.update(self.dt) self.root_layer.draw() def touch_began(self, touch): self.root_layer.remove_layer(self.cloud) self.cloud = Cloud(self) self.cloud.frame.x = self.bounds.w * 0.5 self.cloud.frame.y = self.bounds.h*0.8 scene.run(MyScene())
-
@Sebastian thank you so much for your help with this project. Will add you to the contributors list.
-
@techteej No worries! That's what I love about this forum; you help out if you can, and in return people will help you when you need it :)
-
A study on player death
was created for those interested to helps us give the game an arcade-like feel. A great solution would close out issue #9. https://github.com/tjferry14/Cloud-Jump-2/blob/master/etude_on_player_death.py