I am making a pokemon ripoff in pygame, and I have made a method to test whether the player is in the tall grass. However, I do not know where I should call the method. I have tried in the player move method, in the game loop method, and the update method. When I do it like this, it checks after you have moved out of the grass, which means the first time you move into grass it won't work, and when you first move out of grass it works (when it shouldn't).
in_grass(self) method:
def in_grass(self):
for g in self.game.grass:
if pygame.sprite.collide_rect(self, g):
print(random.randint(1, 10000))
if random.randint(1, 180) <= 25:
self.battle()
player move(self) method:
def move(self, x_change, y_change):
if x_change > 0:
self.dir = 'RIGHT'
if x_change < 0:
self.dir = 'LEFT'
if y_change > 0:
self.dir = 'DOWN'
if y_change < 0:
self.dir = 'UP'
if not self.collide(x_change, y_change):
self.x += x_change
self.y += y_change
image_list = None
if self.dir == 'UP':
image_list = self.image_up
elif self.dir == 'LEFT':
image_list = self.image_left
elif self.dir == 'RIGHT':
image_list = self.image_right
elif self.dir == 'DOWN':
image_list = self.image_down
if image_list:
if self.walkcount >= len(image_list):
self.walkcount = 0
self.image = image_list[self.walkcount]
self.walkcount += 1
self.image.set_colorkey(BLACK)
Main loop:
def events(self):
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_w:
self.player.move(0, -1)
if event.key == pygame.K_a:
self.player.move(-1, 0)
if event.key == pygame.K_s:
self.player.move(0, 1)
if event.key == pygame.K_d:
self.player.move(1, 0)
if event.type == pygame.KEYUP:
self.player.image = self.player.default_images[self.player.dir]
self.player.image.set_colorkey(BLACK)
EDIT: I finally got it working! I can't give myself the bounty, so I guess it will just sit there.
So here is some food for thought.
Little disclaimer,
I am in no way a game dev nor am i a very experienced developer.
In a larger game you might want to keep a good grasp on what is happening where. Now say we have a game full of entities (think of entities as things you see on screen that have some sort of impact on the game). We would wanna update all these entities every frame of the gameloop.
Entities might be rendered, moved, collission checked, affected by player input, animated or whatever functionality your game needs.
The order in which we do this is important for our game, suppose we have a list of entities
entities = []
We could then do something like this where we loop trough each enemy in our gameloop en execute their behaviour function. we pass in all the entities into the check_collision function to make this code a little more understandable.
Version 1
entities = []
def game_loop:
while(true):
for entity in entities:
entity.gather_input()
entity.move()
entity.check_collision(entities)
entity.animate()
entity.render()
Now take a look at this:
Version 2
entities = []
def game_loop:
while(true):
for entity in entities:
entity.gather_input()
for entity in entities:
entity.move()
for entity in entities:
entity.check_collision(entities)
for entity in entities:
entity.animate()
for entity in entities:
entity.render()
The order in which we execute the entities their behaviour has changed. We now have grouped behaviour, we could say we have defined the update fases of our gameloop. We now know all the movement of entities is updated before we check all of their collisions.
Whereas in Version 1 we do all the logic first for 1 entity and then do the next. We might lose relevant data in a single frame using Version 1 because 1 entity might move and check collision wheres it really should've waited till all entities have moved.
Hope this might spark some more research into the rabbithole that is game design/architecture.
Create a boolean in the player's __init__ method callled self.checked = False. Then, in the in_grass method, at the end, set self.checked = True. In the update method, if not self.checked: self.in_grass. Finally in the move method, at the very start, set self.checked = False
I finally got it working, yay!
Related
I'm trying to make my game be able to be played with a xbox one controller, I can make it shoot and exit the game but I'm having trouble with the axis (I've tried with the D-pad but it was the same)
EDIT: Now i can make it move but It's like a frame, and I need to move the axis multiple times to make it move, I want to hold the axis 1 and that the player moves smoothly and it always moves left for some reason.
This is the Player class:
class Player(pygame.sprite.Sprite):
def update(self):
self.speed_x = 0
for event in pygame.event.get():
if event.type == JOYAXISMOTION:
if event.axis <= -1 >= -0.2:
self.speed_x = PLAYER_SPEED
if event.axis <= 1 >= 0.2:
self.speed_x = -PLAYER_SPEED
self.rect.x += self.speed_x
The Joystick only gives you notice of changes to position. So if it's previously sent an event to say that the joystick's left-right axis is at 0.5, then joystick axis is still at 0.5.
So you probably need something like:
PLAYER_SPEED = 7
...
for event in pygame.event.get():
if event.type == JOYAXISMOTION:
if event.axis == 1: # possibly left-right
player.speed_x = PLAYER_SPEED * event.value
elif event.axis == 2: # possibly up-down
player.speed_y = PLAYER_SPEED * event.value
...
# In main loop
movePlayer( player.speed_x, player.speed_y )
How the axes map to up/down and left/right is dependent on the joystick type. So maybe you will need to reverse the mapping.
So im trying to make a local multiplayer game, i have created the player class which works fine so far.
Now im trying to do a bullet class to create different types of shots for the player's spaceships, however upon calling my bullet, it only gets drawn to the screen where the player was at the start of the game and it doesnt move (so far just trying to program it to move)
Here is the code:
import pygame
pygame.init()
#Display screen
screen = pygame.display.set_mode((800, 600))
pygame.display.set_caption("Space Arena")
background = pygame.image.load(r'C:\Users\Bruno\Documents\PythonProjects\Pygame\Multiplayer\Images\background.png').convert_alpha()
playerImg = pygame.image.load(r'C:\Users\Bruno\Documents\PythonProjects\Pygame\Multiplayer\Images\Player.png').convert_alpha()
player2Img = pygame.image.load(r'C:\Users\Bruno\Documents\PythonProjects\Pygame\Multiplayer\Images\Player2.png').convert_alpha()
regular_bullet = pygame.image.load(r'C:\Users\Bruno\Documents\PythonProjects\Pygame\Multiplayer\Images\bullet.png').convert_alpha()
class Player:
def __init__(self, image, x, y, isPlayer1, isPlayer2):
self.image = image
self.x = x
self.y = y
self.isPlayer1 = isPlayer1
self.isPlayer2 = isPlayer2
def Move(self):
key_states = pygame.key.get_pressed()
x_change = 0.2
y_change = 0.2
if self.isPlayer1:
if key_states[pygame.K_a]:
self.x += -x_change
if key_states[pygame.K_d]:
self.x += x_change
if key_states[pygame.K_w]:
self.y += -y_change
if key_states[pygame.K_s]:
self.y += y_change
elif self.isPlayer2:
if key_states[pygame.K_LEFT]:
self.x += -x_change
if key_states[pygame.K_RIGHT]:
self.x += x_change
if key_states[pygame.K_UP]:
self.y += -y_change
if key_states[pygame.K_DOWN]:
self.y += y_change
def draw(self, screen):
screen.blit(self.image,(self.x,self.y))
def ColorPlayer(self ,image):
if self.isPlayer1:
self.image.fill((190,0,0,100), special_flags = pygame.BLEND_ADD)
if self.isPlayer2:
self.image.fill((0,0,190,100), special_flags = pygame.BLEND_ADD)
class Bullet():
def __init__(self, image, bulletx, bullety):
self.image = image
self.bulletx = bulletx
self.bullety = bullety
self.bullet_state = "ready"
def draw(self, screen):
if self.bullet_state == "fire":
screen.blit(self.image,(self.bulletx,self.bullety))
def shoot(self):
change_x = 0.9
if self.bullet_state == "fire":
self.bulletx += change_x
if self.bulletx > 800:
self.bullet_state = "ready"
def redrawWindow(screen, player, player2, background, player_bullet):
screen.fill((255,255,255))
screen.blit(background,(0,0))
player.draw(screen)
player2.draw(screen)
player_bullet.draw(screen)
pygame.display.update()
def Main():
done = False
player = Player(playerImg,200,200,True,False)
player2 = Player(player2Img,600,200,False,True)
player1_bullet = Bullet(regular_bullet, player.x, player.y)
while not done:
player.Move()
player2.Move()
player.ColorPlayer(playerImg)
player2.ColorPlayer(player2Img)
redrawWindow(screen, player, player2, background, player1_bullet)
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_SPACE:
if player1_bullet.bullet_state is "ready":
player1_bullet.bullet_state = "fire"
player1_bullet.shoot()
Main()
It looks like shoot is effectively the "update" function for your bullet, and the code inside would need to be called every frame (like with player.Move()) for the bullet to keep moving after it's fired.
The only code you have that changes the bullet position is this bit inside the shoot method.
def shoot(self):
change_x = 0.9
if self.bullet_state == "fire":
self.bulletx += change_x
Nothing else moves it. You need to have a move function that adjusts it position and call that each frame. In fact your shoot function is a bit odd it looks more like what the move function would look like. Perhaps rename it to move() and call it each frame?
That function is a bit unusual for a shoot() function, since it moves an existing bullet instead of firing a new one. Normally a shoot method would be expected to be a method in the player and to create a new bullet. It does not really make sense for a bullet instance function to shoot and to create a new bullet. Also you seem to have coded it so that there is a single bullet that goes to 800 and then can be re-fired, but you do not have anything that resets the bullets position back to where the player is.
It might make more sense for the 'ready' and 'fire' state to be a state of the player since the player is the one that can or cannot fire. Also the shoot() method is more commonly in the player class and when it fires (the player is in state 'ready') it creates a new Bullet instance at the players position.
The bullet move() method would be called each frame just like the player move() functions are. Normally there would be more than one bullet allowed at a time and so you would keep a list of the and have to iterate through the list of bullets moving them along (and doing collision checking to see if they hit anything). If a bullet goes off the screen or hits something then you remove the bullet from the bullet list.
You have to change 2 things.
The method Bullet.shoot() has to be continuously invoked in the main application loop. Note, this method updates the bullet dependent on its state. If the state of the bullet is "fire" then the bullet moves. Else it stands still.
Update the position of the bullet when it is fired. The bullet always has to start at the position of the player:
def Main():
# [...]
while not done:
# [...]
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_SPACE:
if player1_bullet.bullet_state is "ready":
player1_bullet.bullet_state = "fire"
player1_bullet.bulletx = player.x
player1_bullet.bullety = player.y
player1_bullet.shoot()
This question already has answers here:
Framerate affect the speed of the game
(1 answer)
Pygame snake velocity too high when the fps above 15 [duplicate]
(1 answer)
Closed 2 years ago.
I've been searching for a solution but still can't get it to work. I don't know what's wrong. I want to move my object smoothly just like this:
https://youtu.be/vc1pJ8XdZa0?t=153
but mine is always "teleporting" when moving. Here's part of my code
#Player
playerImg = pygame.image.load('yin.png')
playerX = 0
playerY = 0
playerX_move = playerY_move = 0
playerMoveUnit = 5
def player(x,y):
screen.blit(playerImg,(x,y))
#Game Loop
running =True
while running:
screen.blit(background,(-100,-80))
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_LEFT:
playerX_move-=playerMoveUnit
if event.key == pygame.K_RIGHT:
playerX_move+=playerMoveUnit
if event.key == pygame.K_DOWN:
playerY_move+=playerMoveUnit
if event.key == pygame.K_UP:
playerY_move-=playerMoveUnit
if event.type == pygame.KEYUP:
if event.key == pygame.K_LEFT:
playerX_move+=playerMoveUnit
if event.key == pygame.K_RIGHT:
playerX_move-=playerMoveUnit
if event.key == pygame.K_DOWN:
playerY_move-=playerMoveUnit
if event.key == pygame.K_UP:
playerY_move+=playerMoveUnit
playerX+=playerX_move
playerY+=playerY_move
#Check boundary
if playerX <=-10:
playerX = -10
elif playerX >=(length-70):
playerX = length-70
if playerY <= -20:
playerY = -20
elif playerY >=(width-105):
playerY = width-105
player(playerX,playerY)
pygame.display.update()
just reduce your "playerMoveUnit"
Use pygame.time.Clock() and .tick() to control the flops per second. The framerate argument the .tick() will delay to keep the game running slower than the given ticks per second. For instance:
clock = pygame.time.Clock()
FPS = 60
running =True
while running:
clock.tick(FPS)
# [...]
Furthermore I recommend to use pygame.key.get_pressed() rather than the keyboard events for the movement of the player. pygame.key.get_pressed() returns a sequence of boolean values representing the state of every key.
Get and evaluate the states of the keys in every frame and move the player accordingly:
clock = pygame.time.Clock()
FPS = 60
running =True
while running:
clock.tick(FPS)
screen.blit(background,(-100,-80))
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT]:
playerX -= playerMoveUnit
if keys[pygame.K_RIGHT]:
playerX += playerMoveUnit
if keys[pygame.K_UP]:
playerY -= playerMoveUnit
if keys[pygame.K_DOWN]:
playerY += playerMoveUnit
#Check boundary
if playerX <=-10:
playerX = -10
elif playerX >=(length-70):
playerX = length-70
if playerY <= -20:
playerY = -20
elif playerY >=(width-105):
playerY = width-105
player(playerX,playerY)
pygame.display.update()
I notice that you do not have a clock.tick(...) in your code. That is needed to limit the frame rate.
You need to put it into your main while loop.
Somewhere at the top of the code you want to set a constant that controls your framerate. You'll also need a clock to call tick() on People commonly use a frame rate of 60, so you would want something like this near the top of your code:
FPS = 60
clock = pygame.time.Clock()
Then at the end of your while loop you need a clock.tick(FPS). I tend to put it just before the screen update like this:
while running:
....
clock.tick(FPS)
pygame.display.update()
FPS stands for Frames Per Second. The tick() call remembers the time that you last called it and does not return until an amount of time equal to 1/FPS has passed since the previous call. Putting it after the computation and just before the update() in the while loop ensures the screen updates occur as close as possible to those even 1/FPS time periods. You can find the docs here.
If your game logic per pass does not take very much time, without this limit your game will run very fast (as you discovered). A different issue but just as bad is, if the amount of computing taken each frame varies, then without imposing a constant frame rate the animation would not be smooth since each frame would display for different amounts of time.
Something like this was likely in the tutorial you were working with, but you may have missed it or not realized what it was for and left it out.
Edit:
After you said you had tried this and it still was not working, I looked at it a bit more. There are two other issues I see, though one may be intentional, I am not sure.
You have playerX_move, playerY_move and playerMoveUnit which together appear to be intended to control your speed. It appears that playerMoveUnit is supposed to be the absolute amount and that playerX_move and playerY_move are intended to be that applied to the X and Y directions. That would mean that you would assign playerMoveUnit to either playerX_move or playerY_move depending on the key press, like this:
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_LEFT:
playerX_move = playerMoveUnit
However you are using += or -= instead. This means that each pass you will be increasing your speed by playerMoveUnit. That makes playerMoveUnit an acceleration not a speed and will result in your object moving very fast very quickly. This is likely the cause of the teleporting behaviour, it is just moving VERY fast.
The other issue I want to point out is that on the KEYDOWN you start the object moving in the desired direction, and on KEYUP you don't stop it by setting the speed to 0. Instead you start the speed going the other way. That is likely not the behaviour that you want. Your code will cause it to move one way when the key is pressed and then start moving in reverse when it is released and not stop till it hits the edge or another key is pressed. You likely want something like this instead:
if event.type == pygame.KEYUP:
if event.key == pygame.K_LEFT:
playerX_move = 0
By the way this would be cleaner if you structured the as a class so you could group all the player related information together.
I just noticed that #JohnnyMopp had a comment on your question that pointed out the += -= thing. Though I had not noticed it, he added that before I edited my answer to include that fix.
You are not using sprites, but single image. You need to load multiple images in order to get smooth movement, in this case for player, if you are following this tutorial, you need 9 images for "left" and 9 for "right" movement. Loading can be done e.g. this way:
go_right = []
go_left = []
for i in range(1,10):
sprite_r = pg.image.load("R{}.png".format(i))
sprite_l = pg.image.load("L{}.png".format(i))
go_right.append(sprite_r)
go_left.append(sprite_l)
Then, in Player class define methods for left and right:
def go_left(self):
self.left = True
self.right = False
self.x -= self.v
def go_right(self):
self.right = True
self.left = False
self.x += self.v
Now, in redraw() or whatever you called it, set FPS to 27 for example(for divisibility reason):
clock.tick(27)
if player.right:
window.blit(go_right[player.walk_count//3], (player.x, player.y))
player.walk_count += 1
elif player.left:
window.blit(go_left[player.walk_count//3], (player.x, player.y))
player.walk_count += 1
else:
player.stand()
window.blit(char, (player.x, player.y))
This if's are responsible for displaying appropriate images from the list (loaded at the beginning)
And at the end, in the main_loop you need to handle the events (left and right arrows e.g.):
if keys[pg.K_RIGHT]:
if player.x < win_width - get_player_width: # or constant number
player.go_right()
elif keys[pg.K_LEFT]:
if player.x > 0:
player.go_left()
You will probably need to adjust some of the code but I made it following the given tutorial. I hope this will work for you
EDIT:
After additional info from the comments I managed to run your code, as others already suggested putting clock.tick(FPS) in main_loop solves the issue. Do not forget clock = pygame.time.Clock() line before the loop. Additionally, if you want smoother and animated movement apply the code I provided above
I have written the following code, it seems to me like it should work, but it appears "firedir" is constantly being set to "facing", not just on mouse click. With this code, when the character changes direction, or facing any projectile on the screen also changes direction. (This is in Python 3.3)
What is wrong with the way I wrote it?
Is there a better way to accomplish this?
def update(char1, laser_list, facing, firedir):
keys = pygame.key.get_pressed()
if (keys[pygame.K_LEFT] or keys[pygame.K_a]):
char1.rect.x -= 2
facing = "left"
elif (keys[pygame.K_DOWN] or keys[pygame.K_s]):
char1.rect.y += 2
facing = "down"
elif (keys[pygame.K_UP] or keys[pygame.K_w]):
char1.rect.y -= 2
facing = "up"
elif (keys[pygame.K_RIGHT] or keys[pygame.K_d]):
char1.rect.x += 2
facing = "right"
if laser_list:
#Move the laser
if firedir == "up":
laser.rect.y -= 4
elif firedir == "down":
laser.rect.y += 4
elif firedir == "left":
laser.rect.x -= 4
elif firedir == "right":
laser.rect.x += 4
return(facing)
#main loop
done = False
while not done:
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
if event.type == pygame.MOUSEBUTTONDOWN:
if not laser_list:
laser = Laser(facing)
laser.rect.x = (char1.rect.x + 10)
laser.rect.y = (char1.rect.y + 10)
all_sprites_list.add(laser)
laser_list.add(laser)
firedir = facing
#update sprites
facing = update(char1, laser_list, facing, facing)
This issue is that you're passing facing as firedir, when you should instead be reading the facing that you passed to the Laser constructor. I'm not sure where that is saved (since you don't show the Laser class definition), but I think you want to make update do something like:
def update(char1, laser_list): # fewer parameters
# key reading stuff, as before, which sets the local variable `facing`
for laser in laser_list: # loop over all lasers in the list
if laser.firedir == "up": # check an attribute of the laser for its direction
laser.rect.y -= 4
elif laser.firedir == "down":
laser.rect.y += 4
elif laser.firedir == "left":
laser.rect.x -= 4
elif laser.firedir == "right":
laser.rect.x += 4
return facing # the returned facing will be used for lasers fired in the next frame!
An alternative, which might be easier, is to store a velocity vector on the laser rather than a direction string. Then you can use the same update code regardless of what direction the laser is aimed (even diagonals, if you allow them, will just work). I don't know what the Laser setup code would be for that, but the update logic would be:
laser.rect.x += laser.velocity.x
laser.rect.y += laser.velocity.y
I found a solution to make a sprite move when you hold a key down. The problem is that it forces writing ugly duplicated code. The current solution I found is:
for event in pygame.event.get():
if event.type == KEYDOWN:
keystate = pygame.key.get_pressed()
while keystate[K_RIGHT]:
screen.fill((255,255,255))
pygame.event.get()
for sprite in sprites:
rimage = sprite[1].getimage()
if sprite[2] is None:
x+=3
sprite[1].update(time)
screen.blit(rimage, (x,y))
if sprite[1].isfinished() == True:
sprite[1].reset()
last_dir = "right"
if x >= screen_width - rimage.get_width():
x = screen_width - rimage.get_width()
#update player sprite movement
#update player sprite animation
#update rest of game map
keystate = pygame.key.get_pressed()
time = pygame.time.get_ticks()
pygame.display.update()
The problem is that the while keystate block. It has to be repeated for each direction and the game world needs to be updated in each while block. That is five places where the same code needs to be duplicated....4 directions plus if a key is not pressed. I could wrap it in a function but I was wondering if there was a better way to handle holding down a button in pygame.
The usual way program in pygame is use the events to update the direction, then write the update position code outside events, that way you don't need replicated code.
clock = pygame.time.Clock()
direction = (0,0)
while True: # main loop
for event in pygame.event.get():
if event.type == KEYDOWN:
if event.key == K_RIGHT:
direction = (3, 0)
elif event.key == K_LEFT:
direction = (-3, 0)
elif event.key == K_UP:
direction = (0, 3)
elif event.key == K_DOWN:
direction = (0, -3)
else:
print "Unrecognized key"
if event.type == KEYUP:
direction = (0, 0)
screen.fill((255,255,255))
for sprite in sprites:
rimage = sprite[1].getimage()
if sprite[2] is None:
# Check if new position is inside the screen
new_pos = x + direction[0], y + direction[1]
if new_pos[0] + rimage.get_width() < screen_width:
x = new_pos[0]
if new_pos[1] + rimage.get_height() < screen_height:
y = new_pos[1]
# Draw the sprite
sprite[1].update(time)
screen.blit(rimage, (x,y))
if sprite[1].isfinished() == True:
sprite[1].reset()
last_dir = direction
#update player sprite movement
#update player sprite animation
#update rest of game map
time = pygame.time.get_ticks()
pygame.display.update()
clock.tick(40) # Keep game running at 40 fps
If you want you can achieve the same with keystate, in such case you don't need to process key events.
Pygame suggests or implies the division of programs into 3 parts:
The event handling, updating and drawing.
As pmoleri already said, you simply change the direction of the movement.
In the update function, you should pass in a delta time parameter, to move all the sprites according to the time passed. It is quite important, since the other technique doesn't take into account the variable speed of the processor. Games in DOS have been made this way, so now we need emulators to artificially slow down the processor. The draw part simply draws all the sprites.
This way you have a clear division between these 3 distinc parts in games: player input, game logic(movement, collision, actions etc.) and drawing.
Additionally, pygame.key.get_pressed() can be used.
This returns a list of all the keys currently being held down.
keys = pygame.key.get_pressed()
if keys[pygame.K_w]:
# Move Up
if keys[pygame.K_a]:
# Move Down
... etc ...
This does not need to be in the event section, but probably where the player updates.
This can make the code less clunky and (possible) be more efficient
-Hope I helped!