Trying to display multiple projectiles on screen - python

Essentially, I am trying to display multiple projectiles on the screen for the player to avoid.
At this point, I'm not worried about hit boxes or anything like that, just simply trying to get multiple projectiles to display. It displays one, then the projectile disappears off the screen and never shows back up.
I believe this may be a scope issue, but I'm not entirely sure. I have moved around pretty much every piece of code I can think of to no avail.
class Game:
clock = pygame.time.Clock()
def __init__(self):
self.enemy = Projectile()
self.enemies = []
def loop(self):
self.clock.tick(30)
for enemy in self.enemies:
if self.enemy.y < 925 and self.enemy.x < self.screen_width:
self.enemy.x += self.enemy.velocity
self.enemy.frame_count += 1
else:
self.enemies.pop(self.enemies.index(self.enemy))
if len(self.enemies) < 5:
self.enemies.append(self.enemy)
def render(self):
# other stuff
for enemy in self.enemies:
self.enemy.draw_projectile(self._display_surf)
And then in my projectile file I have:
class Projectile():
# projectile images
def __init__(self):
# stuff
def draw(self, gameDisplay):
if self.frame_count > 118:
self.frame_count = 0
display_window.blit(self.projectile_sprites[self.frame_count],(self.x, self.y))
I'm trying to get multiple projectile-type enemies on the screen. I looked over a few tutorials on how to do this type of thing and I can't get the expected results.

Few things need to be fixed. Not sure these fixs will be enough, but at least they will help.
Class Projectile seems ok sintactically. I cannot speak for the omitted parts, though.
However, you never use the gameDisplay argument in the draw function, but you have a display_window. Are they supposed to be the same thing? If so, use the same name.
I guess display_window / gameDisplay is the display, the surface initialized with pygame.display.set_mode(). If so, is fine.
For the Game class, see the comments I added.
class Game:
clock = pygame.time.Clock()
def __init__(self):
#self.enemy = Projectile() remove this line, is useless.
self.enemies = []
def loop(self):
self.clock.tick(30)
for enemy in self.enemies:
if enemy.y < 925 and enemy.x < self.screen_width:
#no need of self before enemy in this section. Otherwise you
#do not refer to the "enemy" on which you are looping but to
#the attribute "enemy" (which I removed). They are different things.
enemy.x += enemy.velocity
enemy.frame_count += 1
else:
self.enemies.pop(self.enemies.index(enemy))
#I leave this here for now, but remember that editing
#a list on which you are looping is a bad idea in general.
if len(self.enemies) < 5:
self.enemies.append(Projectile())
#each iteration you need to create a new instance of the class.
def render(self):
# other stuff
for enemy in self.enemies:
enemy.draw(self._display_surf)
#again, no self before enemy, same reason of before.
I do not know what is draw_projectile(). I suppose you meant to use the draw() method of Projectile class, it makes sense thos way.
Again self._display_surf is not defined in your code, I supposed is the display which you pass to the draw() function.

Related

Python turtle module is not responding to the keystroke that is being issued

So far, my snake game is doing somewhat okay. One issue is that for some reason the "turtle" isn't responding to the keystrokes, and I really don't know why. I tried a lot of different stuff but it was all useless. The main problem is that I am not entirely sure where the main issue is. What I know for sure is that the problem is most likely from my code, but I can't seem to find it. If you could assist me in solving this issue that would great.
import time
from turtle import Screen, Turtle
STARTING_X_POSITIONS = [0, -20, -40]
MOVEMENT_DISTANCE = 20
class Snake:
def __init__(self):
self.segments = []
self.create_snake()
self.head = self.segments[0]
def create_snake(self):
for i in range(3):
new_snake = Turtle('square')
new_snake.color('RoyalBlue')
new_snake.penup()
new_snake.goto(STARTING_X_POSITIONS[i], 0)
self.segments.append(new_snake)
def move(self):
# We Want The Loop To Start At Index (2) And Decrease Itself Till It Reaches Zero (Excluded)
for snake_index in range(len(self.segments) - 1, 0, -1):
x_pos = self.segments[snake_index - 1].xcor()
y_pos = self.segments[snake_index - 1].ycor()
self.segments[snake_index].goto(x_pos, y_pos)
self.segments[0].forward(MOVEMENT_DISTANCE)
def up(self):
self.head.setheading(90)
def down(self):
self.head.setheading(270)
def left(self):
self.head.setheading(180)
def right(self):
self.head.setheading(0)
def setup_screen(screen):
screen.bgcolor('black')
screen.title('Snake Game')
screen.setup(width=600, height=600)
screen.tracer(0)
def start_game(screen, snake):
setup_screen(screen)
game_on = True
while game_on:
screen.update()
time.sleep(0.1)
snake.move()
def control_snake(screen, snake):
screen.listen()
screen.onkey(key='Up', fun=snake.up)
screen.onkey(key='Down', fun=snake.down)
screen.onkey(key='Left', fun=snake.left)
screen.onkey(key='Right', fun=snake.right)
screen.exitonclick()
def main():
screen = Screen()
snake = Snake()
start_game(screen, snake)
control_snake(screen, snake)
if __name__ == '__main__':
main()
This is a good example of the importance of minimizing the code when you debug. Consider the code here:
def start_game(screen, snake):
game_on = True
while game_on: # infinite loop
screen.update()
time.sleep(0.1)
snake.move()
def control_snake(screen, snake):
# add key listeners, the failing behavior
def main():
# ...
start_game(screen, snake)
control_snake(screen, snake)
main calls start_game, but start_game has an infinite while loop in it. game_on is never set to False, and so control_snake will never be reached.
Try adding key listeners before you go into your infinite rendering loop, not after.
Moving control_snake ahead of start_game introduces a new problem, which is that screen.exitonclick() is part of control_snake, but if control_snake is called before start_game, then the screen.exitonclick() blocks and prevents start_game from running. So we need to remove screen.exitonclick().
But there's a better way to trigger repeated events than while/sleep, which is screen.ontimer. This lets you defer control back to your main loop and block on a screen.exitonclick() call. This post shows an example.
Taking a step back, here are a few other tips that address underlying misconceptions and root causes of your bugs.
It's a bit odd that setup_screen is called from start_game. I'd call setup_screen from main to decouple these. I can imagine a case where we want to set up the screen once, but restart the game multiple times, for example, after the snake dies.
In general, I'd worry less about breaking things out into functions until you have the basic code working. Don't write abstractions just because you've heard that functions longer than 5 or 6 lines are bad. The functions should have a clear, singular purpose foremost and avoid odd dependencies.
For example, control_snake should really be called add_snake_controls_then_block_until_exit or something like that, because not only does it add snake controls (it doesn't really "control the snake" exactly, it registers the controls that do so), it also blocks the whole script and runs turtle's internal update loop until the user clicks the window. This might sound pedantic, but if you'd named this function to state exactly what it does, the bug would be much more obvious, with the side benefit of clearer code in general.
Your game loop code:
while game_on:
screen.update()
time.sleep(0.1)
snake.move()
is a bit confusing to follow. The usual rendering sequence is:
update positions
rerender
sleep/defer control until the next update cycle
I suggest the clearer
while game_on:
snake.move() # update positions
screen.update() # render the frame
time.sleep(0.1) # defer/pause until the next tick
Another tip/rule of thumb is to work in small chunks, running your code often. It looks like you wrote a huge amount of code, then ran it for the first time and weren't sure where to begin debugging. If I were writing a snake game, I wouldn't worry about the tail logic until I've set up the head and established that it works, for example.
If you do wind up with a lot of code and a bug in spite of your best efforts, systematically add prints to see where control is reached. If you added a print in control_snake, you'd see it never gets called, which pretty much gives away the problem (and therefore its solution).
Another debugging strategy is to remove code until the problem goes away, then bring back the last chunk to see exactly what the problem was.
All that said, your Snake class seems purposeful and well-written.
Here's my rewrite suggestion:
import turtle
class Snake:
def __init__(self, grid_size, initial_x_positions):
self.grid_size = grid_size
self.create_snake(initial_x_positions)
def create_snake(self, initial_x_positions):
self.segments = []
for x in initial_x_positions:
segment = turtle.Turtle("square")
segment.color("RoyalBlue")
segment.penup()
segment.goto(x, 0)
self.segments.append(segment)
self.head = self.segments[0]
def move(self):
for i in range(len(self.segments) - 1, 0, -1):
x_pos = self.segments[i - 1].xcor()
y_pos = self.segments[i - 1].ycor()
self.segments[i].goto(x_pos, y_pos)
self.head.forward(self.grid_size)
def up(self):
self.head.setheading(90)
def down(self):
self.head.setheading(270)
def left(self):
self.head.setheading(180)
def right(self):
self.head.setheading(0)
def create_screen():
screen = turtle.Screen()
screen.tracer(0)
screen.bgcolor("black")
screen.title("Snake Game")
screen.setup(width=600, height=600)
screen.listen()
return screen
def main():
initial_x_positions = 0, -20, -40
frame_delay_ms = 80
grid_size = 20
screen = create_screen()
snake = Snake(grid_size, initial_x_positions)
screen.onkey(key="Up", fun=snake.up)
screen.onkey(key="Down", fun=snake.down)
screen.onkey(key="Left", fun=snake.left)
screen.onkey(key="Right", fun=snake.right)
def tick():
snake.move()
screen.update()
turtle.ontimer(tick, frame_delay_ms)
tick()
screen.exitonclick()
if __name__ == "__main__":
main()
Since there's no restarting condition or accompanying logic, this will probably need to be refactored to allow for a "game over" screen and resetting the snake or something like that, but at least it's solid and there aren't a lot of premature abstractions to have to reason about.
I got it working as follows
import turtle
STARTING_X_POSITIONS = [0, -20, -40]
MOVEMENT_DISTANCE = 20
frame_delay_ms = 80
class Snake:
def __init__(self, screen):
self.screen = screen
self.control_snake()
self.segments = []
self.create_snake()
self.head = self.segments[0]
def create_snake(self):
for i in range(3):
new_snake = turtle.Turtle('square')
new_snake.color('RoyalBlue')
new_snake.penup()
new_snake.goto(STARTING_X_POSITIONS[i], 0)
self.segments.append(new_snake)
def control_snake(self):
self.screen.onkey(key='Up', fun=self.up)
self.screen.onkey(key='Down', fun=self.down)
self.screen.onkey(key='Left', fun=self.left)
self.screen.onkey(key='Right', fun=self.right)
self.screen.listen()
def move(self):
# We Want The Loop To Start At Index (2) And Decrease Itself Till It Reaches Zero (Excluded)
for snake_index in range(len(self.segments) - 1, 0, -1):
x_pos = self.segments[snake_index - 1].xcor()
y_pos = self.segments[snake_index - 1].ycor()
self.segments[snake_index].goto(x_pos, y_pos)
self.segments[0].forward(MOVEMENT_DISTANCE)
def up(self):
self.head.setheading(90)
def down(self):
self.head.setheading(270)
def left(self):
self.head.setheading(180)
def right(self):
self.head.setheading(0)
class ScreenSetup:
def __init__(self):
self._screen = turtle.Screen()
self.setup_screen()
def setup_screen(self):
self._screen.bgcolor('black')
self._screen.title('Snake Game')
self._screen.setup(width=600, height=600)
self._screen.tracer(0)
#property
def screen(self):
return self._screen
def run_snake(snake, screen):
snake.move()
screen.update()
turtle.ontimer(lambda: run_snake(snake, screen), frame_delay_ms)
def main():
screen = ScreenSetup().screen
snake = Snake(screen)
run_snake(snake, screen)
screen.exitonclick()
if __name__ == '__main__':
main()

How can I pass a value from one class to another using onkeypress?

I am trying to build a game where a ship should shoot some stuff, I am trying to get the ship position and pass it to the function that generate the shot, but I can't do this, I tried the below methods:
ship.py
def __init__(self):
super().__init__()
self.ship = Turtle(shape='turtle')
self.ship.penup()
self.ship.color('white')
self.ship.goto(0, -250)
self.ship.left(90)
self.x = self.ship.xcor()
def get_pos(self):
return self.x
shoot.py
def shoot_now(self, pos):
print(pos)
First try with ship.get_pos()
main.py
screen.onkeypress(functools.partial(shots.shoot_now, ship.get_pos()), 'space')
Second try wuth ship.get_pos
screen.onkeypress(functools.partial(shots.shoot_now, ship.get_pos), 'space')
The first one results in a lot of 0 and the second one result something like that:
<bound method Ship.get_pos of <ship.Ship object at 0x000001F05339A520>>
I know that the function get_pos is working because everytime I run it alone it prints the right position, but when I try to use the onkeypress to pass the value to shoot.py it does not work.
Does someone know a way to make it work?
My guess is you're locking in the ship's position once, when onkeypress() is called, and all subsequent key presses are using that original value.
Besides the ship's position, you also need to know the ship's heading when a bullet is fired. To simplify programming, I'd have both the ship and bullet be turtles, rather than contain turtles, though either will work.
If you want to keep the bullet's motion from locking out the ship's motion, you're going to need a timer. Here's a minimal implementation that gets you a single moving bullet that doesn't freeze the ship's motion:
from turtle import Screen, Turtle
class Bullet(Turtle):
def __init__(self):
super().__init__()
self.hideturtle()
self.shape('circle')
self.shapesize(0.25)
self.penup()
self.origin = (0, 0)
def fire(self, origin, heading):
self.origin = origin
self.setheading(heading)
self.setposition(origin)
self.showturtle()
self.move()
def move(self):
if self.distance(self.origin) < 500:
self.forward(5)
screen.ontimer(self.move, 5)
else:
self.hideturtle()
class Ship(Turtle):
def __init__(self):
super().__init__()
self.shape('triangle')
self.penup()
self.sety(-250)
self.left(90)
def shoot_now(self):
if not bullet.isvisible():
bullet.fire(self.position(), self.heading())
screen = Screen()
bullet = Bullet()
ship = Ship()
screen.onkeypress(lambda: ship.right(5), 'Right')
screen.onkeypress(lambda: ship.left(5), 'Left')
screen.onkeypress(ship.shoot_now, 'space')
screen.listen()
screen.mainloop()
To extend this to multiple live bullets, you can keep a list of inactive bullets, pull from it when you fire, add back to it when the bullet is spent. This also gives you control over the number of bullets in play.

How can I make a weapon shoot using Ursina?

I am currently using Ursina Game Engine and am trying to make a basic FPS game. In the game, there is obviously a gun and a bullet.
The system I have for shooting the gun isn't working. First, it isn't moving forward the way I want it, and second, it isn't able to detect collision sufficiently.
I am wondering how I can make the gun shoot a bullet forward properly, and also how I can make it tell me when it hits something.
Here is the code I am using:
def shoot():
print(globalvar.currentmag, globalvar.total_ammo)
if globalvar.currentmag > 0:
bullet = Entity(parent=weapon, model='cube', scale=0.1, color=color.brown, collision = True, collider = 'box')
gunshot_sfx = Audio('Realistic Gunshot Sound Effect.mp3')
gunshot_sfx.play()
bullet.world_parent = scene
bullet.y = 2
for i in range(3000):
bullet.position=bullet.position+bullet.right*0.01
globalvar.currentmag -= 1
hit_info = bullet.intersects()
print(hit_info)
if hit_info.hit:
print("bullet hit")
destroy(bullet, delay=0)
destroy(bullet, delay=1)
else:
reload()
I have tried using the raycast method in Ursina but that did not work.
Don't update the bullet's position right after you create it. Instead, use its animate_position() method to let Ursina update it. Then you can check for collisions in the global update() function. Make sure that all involved entities have a collider and that they're accessible globally.
bullet = None
def input(key):
global bullet
if key == 'left mouse down':
bullet = Entity(parent=weapon, model='cube', scale=.1, color=color.black, collider='box')
bullet.world_parent = scene
bullet.animate_position(bullet.position+(bullet.forward*500), curve=curve.linear, duration=2)
destroy(bullet, delay=2)
def update():
if bullet and bullet.intersects(wall).hit:
hit_info = bullet.intersects(wall)
print('hit wall')
destroy(bullet)
I adapted this example from the demo code in the FirstPersonController class. I'm not sure if there's a way without specifying the other entity when calling bullet.intersects(). I think it should work because the hit_info object has various properties, among them entities but I couldn't get it to work myself.

Overlay tiles onto sprite in pygame

I am creating a tile-based 2d overworld for a game - heavily influenced by Pokemon - using pygame/python, Tiled for .tmx files, and the tmx library by Richard Jones. The code I'm using is mostly based on this wonderful demo of Pallet Town in python.
The game runs just fine, however, I am having problems with making tiles on the map (e.g. houses, trees) overlap the player sprite when it would make sense for the player sprite to disappear behind them. For example: in the image here, principles of depth perception would tell us that the house in the foreground should occlude the player in the background, but because the map is 2D there is no depth and therefore no occlusion. I would love to add depth, but seeing as I'm very new to pygame (and python in general), I'm at a loss at how to draw the relevant foreground objects over the sprite.
Luckily I'm not alone in this problem and plenty of documentation on possible solutions exist. For example:
this StackExchange question
this LibGDX tutorial
this Unity tutorial
However, this code isn't typically written for python and I'm not sure how to implement it in my situation. Sorting/drawing by z position (or by a 'depth' property) seems like the most sensible thing to do, but looking at the tmx library I can only find x and y values mentioned. Adding the player sprite to an empty object layer in Tiled is also a solution, but once again I'm unsure of how to do this and all my attempts have led to error messages. (Attempts not detailed here because I honestly don't know what I did and it didn't work anyway.)
My current code is as follows:
class Player(pygame.sprite.Sprite):
def __init__(self, location, collStart, orientation, *groups):
super(Player, self).__init__(*groups)
self.image = pygame.image.load('sprites/player.png')
self.imageDefault = self.image.copy()
self.rect = pygame.Rect(location, (26,26))
self.collider = pygame.Rect(collStart, (13,13))
self.orient = orientation
self.holdTime = 0
self.walking = False
self.dx = 0
self.step = 'rightFoot'
# Set default orientation
self.setSprite()
self.speed = pygame.time.get_ticks() + 50 # slows down walking speed
by .5 sec (current time + 50 ms)
def setSprite(self):
# this function contains information about where to find which sprite
in the sprite sheet, probably not relevant here.
def update(self, dt, game):
key = pygame.key.get_pressed()
if pygame.time.get_ticks() >= self.speed:
self.speed = pygame.time.get_ticks() + 50
# Setting orientation and sprite based on key input, removed the
#code here because it wasn't relevant
#[....]
# Walking mode enabled if a button is held for 0.1 seconds
if self.holdTime >= 100:
self.walking = True
lastRect = self.rect.copy()
lastColl = self.collider.copy() # collider covers the bottom section of the sprite
# Code for walking in the direction the player is facing, not relevant here
#[....]
# Collision detection:
# Reset to the previous rectangle if player collides
# with anything in the foreground layer
if len(game.tilemap.layers['triggers'].collide(self.collider,
'solid')) > 0:
self.rect = lastRect
self.collider = lastColl
# Area entry detection, loads dialog screen from the dialog file:
elif len(game.tilemap.layers['triggers'].collide(self.collider,
'entry')) > 0:
entryCell = game.tilemap.layers['triggers'].find('entry')[0]
game.fadeOut()
run()
pygame.quit()
quit()
return
if self.dx == 16:
# Makes the player appear to take steps w/ different feet, not relevant here
#[....]
# After traversing 32 pixels, the walking animation is done
if self.dx == 32:
self.walking = False
self.setSprite()
self.dx = 0
game.tilemap.set_focus(self.rect.x, self.rect.y)
class Game(object):
def __init__(self, screen):
self.screen = screen
def initArea(self, mapFile):
"""Load maps and initialize sprite layers for each new area"""
self.tilemap = tmx.load(mapFile, screen.get_size())
self.players = tmx.SpriteLayer()
self.objects = tmx.SpriteLayer()
# In case there is no sprite layer for the current map
except KeyError:
pass
else:
self.tilemap.layers.append(self.objects)
# Initializing player sprite
startCell = self.tilemap.layers['triggers'].find('playerStart')[0]
self.player = Player((startCell.px, startCell.py), (startCell.px,
startCell.bottom-4),
startCell['playerStart'], self.players)
self.tilemap.layers.append(self.players)
self.tilemap.set_focus(self.player.rect.x, self.player.rect.y)
def main(self):
clock = pygame.time.Clock()
self.initArea('test tilemap.tmx')
while 1:
dt = clock.tick(30)
for event in pygame.event.get():
if event.type == pygame.QUIT:
return
if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE:
return
self.tilemap.update(dt, self)
screen.fill((0,0,0))
self.tilemap.draw(self.screen)
pygame.display.flip()
Once again, I'm using the tmx library found here. Maybe something needs to be changed there? Hopefully, someone can help me figure this out. It's definitely possible, as shown by this pokemon clone in python (no source code available, sadly).
Also, first-time StackOverflow user so let me know if I'm committing any faux-passes :)
Figured it out! As suggested by Kingsley in the comments, the solution was to change the draw order of the layers. Layers were being drawn in order of a list in the Layers class, with the player sprite having the highest index and thus being drawn on top of everything else. Moving the player between the background and foreground layer in the list made it appear behind the foreground objects.
To do this I added the following code to the initArea function in the Game class:
def initArea(self, mapFile):
"""Load maps and initialize sprite layers for each new area"""
self.tilemap = tmx.load(mapFile, screen.get_size())
self.players = tmx.SpriteLayer()
self.objects = tmx.SpriteLayer()
# Initializing player sprite
startCell = self.tilemap.layers['triggers'].find('playerStart')[0]
self.player = Player((startCell.px, startCell.py), (startCell.px, startCell.bottom-4),
startCell['playerStart'], self.players)
foregroundItem = self.tilemap.layers.__getitem__("foreground") # finds the layer called foreground
foregroundIndex = self.tilemap.layers.index(foregroundItem) # finds the position of the foreground layer in the Layers list (Layers class specified in .tmx file)
self.tilemap.layers.insert(foregroundIndex-1, self.players) # move the Player layer one layer below the foreground layer
self.tilemap.set_focus(self.player.rect.x, self.player.rect.y)
I'll experiment a bit more tonight, but for now this solution seems to work. Thanks for the help!

Shooting bullets in pygame

I'm making my own space invaders game and so far I've been able to move my ship around using the mouse. However, I still can't shoot. Here's my game loop.
def game_loop():
x=0
y=0
xlist=[]
ylist=[]
while True:
mouseclk=pygame.mouse.get_pressed()
game_display.fill(white)
for event in pygame.event.get():
if event.type==pygame.QUIT:
pygame.quit()
quit()
x, y = pygame.mouse.get_pos()
xlist.append(x)
ylist.append(y)
if x>=display_width-40:
x=display_width-40
if y>=display_height-48:
y=display_height-48
if pygame.mouse.get_focused()==0:
game_display.blit(spaceship, (x, y))
elif pygame.mouse.get_focused()==1:
game_display.blit(spaceshipflames, (x, y))
pygame.display.update()
if pygame.mouse.get_focused()==0:
pause()
clock.tick(500)
I've tried using the following code inside my game loop:
if mouseclk[0]==1:
shoot.play()
while True:
pygame.draw.circle(game_display, white, (x+20, y-2), 5)
pygame.draw.circle(game_display, red, (x+20, y-7), 5)
y-=5
if y<=0:
break
pygame.display.update()
clock.tick(400)
But the end result is very glitchy and doesn't allow me to shoot multiple bullets without making the game choppy.
Is there a way to run both loops at the same time, or a completely different way to implement shooting?
I'd recommend making use of classes (especially for games) and splitting up your code into smaller functions.
When making a game, each class should represent some type of object in the game, for example a ship or a bullet here. Using classes should help with this problem of multiple bullets causes glitches.
Breaking into smaller functions will make your code much easier to read and update as it grows. Try to stick to the Single Responsibility Principle as much as possible.
How you might implement shooting with these things in mind:
bullets = []
class Bullet:
def __init__(self, position, speed):
self.position = position
self.speed = speed
def move(self):
self.position[1] += self.speed
class Ship:
def __init__(self, position, bullet_speed):
self.position = position
self.bullet_speed = bullet_speed
def shoot(self):
new_bullet = Bullet(self.position, self.bullet_speed)
bullets.append(new_bullet)
Where the position variables have the form [x,y]. Then to move your bullets forward, put this line somewhere in your main game loop:
for bullet in bullets:
bullet.move()
And loop over all the bullets and draw each to the screen to render them.
This isn't the most detailed example, but hopefully it's enough to get you going in the right direction.

Categories

Resources