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.
Scene, Game Collision & Gravity Issues
-
Hey! I started my first little project with Pythonista, my goal at the moment is to create a working template for a 2D platformer-type game. It's more or less running fine there just some problems with the gravity and collision.
The problems are:
-
The gravity effects the player at the beginning which is fine and as soon as the player collide with the ground the variable
self.onGround = True
. The problem is this variable don't get reset to False after the player is not on the ground anymore which results in the player floating in the sky. As soon as I jump, the gravity is working again till I hit the ground. I tried to change theself.onGround = False
in theupdate
method but this results that other functions don't work probably. -
The other problem is that as soon as I'm falling and move left or right the
collision_left_right
don't work anymore which results that the player get spawned on top of the tile when I hit it on the right or left. I guess this might due that I changevelocity.x
andvelocity.y
at the same time.
I have the bad feeling I need something like a
hitTest
function which checks where I hit the tile.
Some side bugs:LANDSCAPE
don't really work probably for me.I'm also open for some recommendations regarding coding style, first time I work with Classes and Methods. I want the code as clean as possible.
Thanks in advance!
(Sorry for my English)If you want to try out the code, the game is running as soon as you touch the screen.
Code:
import scene from scene import * import sound import random import math import level import ui A = Action # Constants PI = math.pi #Screen Size SCREEN_W = get_screen_size().w SCREEN_H = get_screen_size().h # Amount of frames till you can shoot again WAIT_TIME = 30 BULLET_SPEED = 10 ROTATION_LEFT = PI/2 ROTATION_RIGHT = 3*PI/2 # Gravity GRAVITY = 4 class Buttons(SpriteNode): ''' Buttons Class ''' def __init__(self, name, appearance, position, scale, alpha, buttons): SpriteNode.__init__(self, appearance) self.name = name self.position = position self.z_position = 3 self.scale = scale self.alpha = alpha buttons.append(self) def __str__(self): return self.name class Buttons_Text(LabelNode): def __init__(self, name, text, button, buttons_text): LabelNode.__init__(self, text, font=('Futura', 13), color='white') self.name = name self.position = (button.position.x, button.position.y - 50) self.z_position = 1 buttons_text.append(self) class Brick(ShapeNode): def __init__(self, name, position, z_position, brick_w, brick_h): path = ui.Path.rect(position[0], position[1], brick_w, brick_h) ShapeNode.__init__(self, path, '#b7b7b7', 'clear') self.name = name self.position = position self.z_position = z_position def __str__(self): return self.name class Map(ShapeNode): def __init__(self, walls): ''' Generates the map ''' lines = level.level.splitlines() brick_w = 40 brick_h = 40 col = 0 row = 0 for line in lines: for i in line: if i == "W": x = 17 + row * brick_w y = 800 - col * brick_h wall = Brick('Brick', (x, y), 0, brick_w, brick_h) walls.append(wall) row += 1 else: x = 17 + row * brick_w y = 800 - col * brick_h row += 1 col += 1 row = 0 class Entity(SpriteNode): ''' Generate an Entity ''' def __init__(self, appearance): SpriteNode.__init__(self, appearance) class Bullet(Entity): def __init__(self, name, appearance, x, y, rotation): super().__init__(appearance) self.position = (x, y) self.rotation = rotation def update(bullets_left, bullets_right): for bullet in list(bullets_left): new_x = bullet.position.x - BULLET_SPEED if new_x >= 0 and new_x <= SCREEN_W: bullet.position = (new_x, bullet.position.y) else: bullet.remove_from_parent() bullets_left.remove(bullet) for bullet in list(bullets_right): new_x = bullet.position.x + BULLET_SPEED if new_x >= 0 and new_x <= SCREEN_W: bullet.position = (new_x, bullet.position.y) else: bullet.remove_from_parent() bullets_right.remove(bullet) class Player(Entity): ''' Player Class ''' def __init__(self, name, appearance, position, z_position): super().__init__(appearance) self.name = name self.position = position self.z_position = z_position self.velocity = Vector2(0, 0) self.speed = 5 self.jump_strength = 100 self.onGround = False # Controls self.leftKey = False self.rightKey = False self.jumpKey = False self.shootKey = False def move_left(self, buttons, touches): for touch in touches.values(): if touch.location in buttons[0].bbox: self.leftKey = True self.velocity.x = -self.speed new_x = self.position.x + self.velocity.x if new_x >= 0 and new_x <= SCREEN_W: self.position = (new_x, self.position.y) def move_right(self, buttons, touches): for touch in touches.values(): if touch.location in buttons[1].bbox: self.rightKey = True self.velocity.x = self.speed new_x = self.position.x + self.velocity.x if new_x >= 0 and new_x <= SCREEN_W: self.position = (new_x, self.position.y) def jump(self, buttons, touches): for touch in touches.values(): if touch.location in buttons[2].bbox and self.onGround: self.jumpKey = True self.onGround = False self.velocity.y = self.jump_strength new_y = self.position.y + self.velocity.y action = A.move_to(self.position.x, new_y, 1, TIMING_LINEAR) if new_y <= SCREEN_H: self.run_action(action) def shoot(self, instance, player_direction, buttons, touches, bullets_left, bullets_right): for touch in touches.values(): if touch.location in buttons[3].bbox: self.shootKey = True if player_direction.position.x < self.position.x and instance.frame_counter >= WAIT_TIME: bullet = Bullet("Bullet Right", 'spc:LaserRed10', self.position.x - 20, self.position.y - 40, ROTATION_LEFT) bullets_left.append(bullet) instance.add_child(bullet) instance.frame_counter = 0 if player_direction.position.x > self.position.x and instance.frame_counter >= WAIT_TIME: bullet = Bullet("Bullet Left", 'spc:LaserRed10', self.position.x + 20, self.position.y - 40, ROTATION_RIGHT) bullets_right.append(bullet) instance.add_child(bullet) instance.frame_counter = 0 def check_touch(self, buttons, touches): #print(touches.values()) #print(self.leftKey, self.rightKey, self.jumpKey, self.shootKey, self.onGround) if len(touches.values()) > 0: for touch in touches.values(): if touch.location not in buttons[0].bbox or touch: self.leftKey = False if touch.location not in buttons[1].bbox: self.rightKey = False if not touch.location in buttons[2].bbox: self.jumpKey = False if not touch.location in buttons[3].bbox: self.shootKey = False else: self.leftKey = False self.rightKey = False self.jumpKey = False self.shootKey = False def check_velocity(self): if not self.leftKey and not self.rightKey: self.velocity = Vector2(0, self.velocity.y) if not self.jumpKey and self.onGround: self.velocity = Vector2(self.velocity.x, 0) def collision_left_right(self, player_bbox, walls): for w in walls: if self.bbox.intersects(w.bbox): if self.velocity.x > 0: new_x = w.bbox.min_x - self.bbox.w / 2 self.position = (new_x, self.position.y) self.velocity.y = 0 #print("collide right") if self.velocity.x < 0: new_x = w.bbox.max_x + self.bbox.w / 2 self.position = (new_x, self.position.y) self.velocity.y = 0 #print("collide left") def collision_up_down(self, player_bbox, walls): for w in walls: if self.bbox.intersects(w.bbox): if self.velocity.y > 0: new_y = w.bbox.min_y - self.bbox.h / 2 self.position = (self.position.x, new_y) self.onGround = False #print("collide top", self.onGround) if self.velocity.y < 0: new_y = w.bbox.max_y + self.bbox.h / 2 self.position = (self.position.x, new_y) self.onGround = True self.velocity.y = 0 #print("collide down", self.onGround) def gravity(self): ''' Describes the gravity''' if not self.onGround: self.velocity.y = -GRAVITY new_y = self.position.y + self.velocity.y if new_y >= 0: self.position = (self.position.x, new_y) class Hitbox(ShapeNode): ''' Creates a visible hitbox for an entity based on the bbox of the entity. Goal is a better fitted hitbox in the future for collision. entity = Object, Player, Enemy etc. entity_objects = List of objects for that entity dw, dh = how much smaller the widht/height of the hitbox should be (based on the bbox of the entity) ''' def __init__(self, entity, entity_objects, dw, dh): self.dw = dw self.dh = dh self.width = entity.bbox.w - self.dw self.height = entity.bbox.h - self.dh path = ui.Path.rect(0, 0, self.width, self.height) ShapeNode.__init__(self, path) self.fill_color = 'white' self.stroke_color = 'clear' self.position = (entity.position.x, entity.position.y - self.dh/2) self.alpha = 0.1 entity_objects.append(self) def bbox(self, entity): box = Rect(self.position.x, self.position.y, self.width, self.height) return box def update(self, entity, rect): self.position = (entity.position.x, entity.position.y - self.dh/2) rect.x = self.position.x - entity.bbox.w / 2 rect.y = self.position.y - entity.bbox.h / 2 class Entity_Direction(ShapeNode): ''' Creates a object which indicates the direction of entity. dh = for adjusting the height ''' def __init__(self, entity, entity_objects): path = ui.Path.rect(0, 0, entity.size.w, 20) ShapeNode.__init__(self, path) self.dh = 40 self.fill_color = 'white' self.stroke_color = 'clear' self.position = (entity.position.x + 20, entity.position.y - self.dh) self.alpha = 0.1 # Last position (True = right, False = left) self.last_direction = True entity_objects.append(self) def update(self, entity): if entity.velocity.x > 0: self.position = (entity.position.x + 20, entity.position.y - self.dh) self.last_direction = True if entity.velocity.x < 0: self.position = (entity.position.x - 20, entity.position.y - self.dh) self.last_direction = False if entity.velocity.x == 0: if self.last_direction: self.position = (entity.position.x + 20, entity.position.y - self.dh) if not self.last_direction: self.position = (entity.position.x - 20, entity.position.y - self.dh) class Game (Scene): def setup(self): # Game running self.game_running = False # Frame Counter to limit the amount of shot bullets self.frame_counter = 0 # Lists for handling different actions & other functions self.walls = [] self.buttons= [] self.buttons_text = [] self.player_objects = [] self.bullets_left = [] self.bullets_right = [] # Generate Map map = Map(self.walls) # Buttons (order is important) self.left_button = Buttons("Left Button", 'iow:arrow_left_a_256', (80, 80), 0.5, 0.25, self.buttons) self.right_button = Buttons("Right Button", 'iow:arrow_right_a_256', (200, 80), 0.5, 0.25, self.buttons) self.jump_button = Buttons("Jump Button", 'iow:nuclear_256', (1000, 80), 0.3, 0.25, self.buttons) self.shoot_button = Buttons("Shoot Button", 'iow:ios7_plus_256', (1100, 80), 0.3, 0.25, self.buttons) self.jump_text = Buttons_Text('Jump Text', 'Jump', self.jump_button, self.buttons_text) self.shoot_text = Buttons_Text('Shoot Text', 'Shoot', self.shoot_button, self.buttons_text) # Player Objects self.player = Player('Player', 'plf:AlienGreen_front', (100, 200), 1) self.player_visible_bbox = Hitbox(self.player, self.player_objects, 10, 50) self.player_bbox = self.player_visible_bbox.bbox(self.player) self.player_direction = Entity_Direction(self.player, self.player_objects) # Add Childs self.add_child(self.player) for p in self.player_objects: self.add_child(p) for b in self.buttons: self.add_child(b) for b_t in self.buttons_text: self.add_child(b_t) for w in self.walls: self.add_child(w) def did_change_size(self): pass def update(self): if self.game_running == True: # Frame Counter, 60 frames == 1 sec self.frame_counter += 1 #self.player.onGround = False # Game Gravity self.player.gravity() # Player Objects self.player_direction.update(self.player) self.player_visible_bbox.update(self.player, self.player_bbox) # Controls self.player.move_left(self.buttons, self.touches) self.player.move_right(self.buttons, self.touches) self.player.jump(self.buttons, self.touches) self.player.shoot(self, self.player_direction, self.buttons, self.touches, self.bullets_left, self.bullets_right) self.player.check_touch(self.buttons, self.touches) #self.player.check_velocity() # Bullets Bullet.update(self.bullets_left, self.bullets_right) # Game Collision self.player.collision_up_down(self.player_bbox, self.walls) self.player.collision_left_right(self.player_bbox, self.walls) def touch_began(self, touch): if touch.location > (0, 0): self.game_running = True def touch_moved(self, touch): pass def touch_ended(self, touch): pass if __name__ == '__main__': run(Game(), LANDSCAPE, show_fps=True)
And level (save this as
level.py
):level = ''' W W W W W W W W W W W W W W W W W W WWWW W WWW WWWWWWWWWW WWWWWWW WWWWW WWWW '''
-
-
@xvid when I try to run it, error AttributeError: 'Player' object has no attribute 'collision_mix' in
self.player.collision_mix
-
@cvp ups sorry, have edited now it should be working with the given code :)
If there are still some errors, hopefully not, let me know! -
@xvid no errors, but I commented some prints, which, I think, crash the program because too they happen too often.
But, I don't understand the game, surely I'm too old for it 😥👴🏻 -
@cvp for real? 🤔 I commented/removed all prints, it should work in this state as no function rely on a print statement. If you have a look would be great maybe you have more experience :)
BTW it's not a "game" in this current state, just an environment to check if the collision and controls are working.
-
@xvid I didn't see the shoot button on my iPad mini4.
You should set your x,y in function of SCREEN_W and SCREEN_H,
like 0.9*SCREEN_W -
I don't know what to do. As soon as I touch the screen, the alien falls and I don't know how to let it jump up. Sorry, but I'm not used to this kind of game...
-
My fault. I had incorporated the level.y into the script and forgotten to split it into lines...
I didlines = level#.level.splitlines()
instead of
lines = level.splitlines()
-
@cvp ah I see! Yeah I'm using an iPad Pro 11". It could be maybe not compatible in this case with other devices. There should be a "Left", "Right", "Jump" and "Shoot" key on the device screen.
-
@xvid I had Understood, I had put the shoot at x=900 because 1100 is not visible for me
-
If I correctly understand, your first problem is that your alien stays so flying
-
@cvp Yes correct, as soon as the alien touch the ground
self.onGround = True
but will not change anymore expect you press jump. -
@xvid is it not due to velocity.y which is 0?
-
Try
def collision_up_down(self, player_bbox, walls): for w in walls: if self.bbox.intersects(w.bbox): if self.velocity.y > 0: new_y = w.bbox.min_y - self.bbox.h / 2 self.position = (self.position.x, new_y) self.onGround = False print("collide top", self.onGround) if self.velocity.y < 0: new_y = w.bbox.max_y + self.bbox.h / 2 self.position = (self.position.x, new_y) self.onGround = True self.velocity.y = 0 print("collide down", self.onGround) return new_y = self.position.y + self.velocity.y if new_y > 0: self.onGround = False
See the return if intersects, you don't need to continue the loop because I suppose you can't intersect more than one wall
-
You can put the same return if intersects in the
collision_left_right
-
@cvp Thank you very much! Yeah it could be also because
velocity.y
is 0. Your code seem to get the gravity problem fixed but now thecollision_left_right
isn't working probably. You see the alien is teleported to the top of the tile if I run against it from left/rigth.Here is a clip with what I mean: GIF
-
The second problem comes from that if you intercepts with a wall in collision_left_right, you will also intersects in collision_up_down and there you will set the y at up side of the wall
-
Ah you were faster haha thanks!
Yes exactly, so I kinda need a function which distinguish which collision should be triggered, right? -
@xvid I think so
-
You need to figure out which edge of the wall is hit. Or, you should back the player up by -velocity until it clears the hit. Your code always moved to the top or left or bottom of a tile, which only works if velocity is totally horizontal or vertical. So, you need to move only in the direction of the touched edge(careful if more than one edge is touched ), or backwards along velocity might be better.
Better yet-- check for future collisions when moving the player, not after he moved.
Or, overlap of an edge is treated like a really high impulse of acceleration along the direction perpendicular to the edge, which will give you bounce. Though you'd want to also implement friction/damping of some kind.