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
-
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.
-
@JonB I already feared I have to do something like this. I assume the method where I check for future collision might be the best but not the easiest one, right?
I probably have to check out some more advanced tutorials for this 🙃.
-
UPDATE:
So, I managed to create a function (based on this) which indicates which edge of the wall is hit called
HitTest
.Now, in the first option (current state) the problem is that I can't jump anymore.
The other option: when I change in thePlayer
class thejump
method fromif touch.location in buttons[2].bbox and self.onGround
toif touch.location in buttons[2].bbox
, which basically means I don't have to be on the ground to jump, gravity & jump is working fine. The only problem now is as soon as I move and jump I can glitch through the walls.Both option aren't optimal but the last one is definitely closer to my aim.
Any tipps? Or did I miss something crucial?
Here is the 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 def HitTest(entity, object, output = 0): ''' max_x = right edge, min_x = left edge, max_y = top edge, min_y = bottom edge ''' rect_1 = entity.bbox rect_2 = object.bbox if rect_1.max_x >= rect_2.x and rect_1.x <= rect_2.max_x: if rect_1.max_y >= rect_2.y and rect_1.y <= rect_2.max_y: output = 1 if output: origin_1 = rect_1.center() origin_2 = rect_2.center() dx = origin_1.x - origin_2.x dy = origin_1.y - origin_2.y if dx <= 0: #print("Hit from Right") output_1 = 1 if dx >= 0: #print("Hit from Left") output_1 = 2 if dy <= 0: #print("Hit from Top") output_2 = 1 if dy >= 0: #print("Hit from Bottom") output_2 = 2 if abs(dx) > abs(dy): #print("Hit Right or Left") output = output_1 if abs(dx) < abs(dy): #print("Hit Top or Bottom") output = output_2 + 2 return output 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): if len(touches.values()) > 0: for touch in touches.values(): if touch.location not in buttons[0].bbox: 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): #print(self.velocity) 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_right(self, player_bbox, w): new_x = w.bbox.min_x - self.bbox.w / 2 self.position = (new_x, self.position.y) self.velocity.y = 0 #print("collide right") return def collision_left(self, player_bbox, w): new_x = w.bbox.max_x + self.bbox.w / 2 self.position = (new_x, self.position.y) self.velocity.y = 0 #print("collide left") return def collision_top(self, player_bbox, w): 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) return def collision_bottom(self, player_bbox, w): 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 def collision(self, player_bbox, walls): for w in walls: result = HitTest(self, w) '''0 : No Collision, 1 : Collision from Right, 2 : Collision from Left, 3 : Collision from Top, 4 : Collision from Bottom ''' if result == 0: self.onGround = False if result == 1: self.collision_right(player_bbox, w) if result == 2: self.collision_left(player_bbox, w) if result == 3: self.collision_top(player_bbox, w) if result == 4: self.collision_bottom(player_bbox, w) 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. 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) print(self.player_bbox) print(self.player_visible_bbox.position) print(self.player.position) 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 # 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(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)
-
Perhaps this, but to late to study it deeply, good luck
def collision(self, player_bbox, walls): for w in walls: result = HitTest(self, w) '''0 : No Collision, 1 : Collision from Right, 2 : Collision from Left, 3 : Collision from Top, 4 : Collision from Bottom ''' #if result == 0: # self.onGround = False
-
@xvid you may want to look for a good tutorial for platformer physics -- might go through all the important cases to think through.
https://gamedevelopment.tutsplus.com/series/basic-2d-platformer-physics--cms-998 is one, though I haven't read it
-
-
@xvid I think the problem comes from your collision method.
Even if you hit a wall, you continue your loop and if you don't intersect a next wall, you have lost your result.
Trydef collision(self, player_bbox, walls): for w in walls: result = HitTest(self, w) '''0 : No Collision, 1 : Collision from Right, 2 : Collision from Left, 3 : Collision from Top, 4 : Collision from Bottom ''' if result == 0: self.onGround = False if result == 1: self.collision_right(player_bbox, w) if result == 2: self.collision_left(player_bbox, w) if result == 3: self.collision_top(player_bbox, w) if result == 4: self.collision_bottom(player_bbox, w) if result != 0: return # leave loop
-
-
@cvp Shouldn’t those be elifs?
-
@ccc makes no difference in this case. Have tried it already.
-
@ccc You're right but I did not want to improve the Python, only a way to solve the bug. Thus I added the two lines in the same style.