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 = Falsein theupdatemethod 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_rightdon'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.xandvelocity.yat the same time.
I have the bad feeling I need something like a hitTestfunction which checks where I hit the tile.
Some side bugs: LANDSCAPEdon'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
'''
