I have already implemented the invincibility / immunity part for when bullet collides with tank, now I want the sprite to "flash"/less opacity/invisible while immune. Here is the relevant code and TODOs where I want to implement a change in the tank image.
class Tank(GamePhysicsObject):
# Handle respawn
self.time_since_death = pygame.time.get_ticks()
self.immune = False
self.immune_time = 3000
def collision_bullet_tank(arb, space, data):
# Set immunity, save time since death, start flashing
tank.parent.immune = True
tank.parent.time_since_death = pygame.time.get_ticks()
tank.parent.start_flashing()
def post_update(self):
# If tank has been immune for some time, make tank vulnerable again
current_time = pygame.time.get_ticks()
if current_time - self.time_since_death > self.immune_time:
self.immune = False
# TODO: Stop flashing
Basically, my question is how can I make a visual sprite change?
Edit: More specifically, what do I type here?
def start_flashing(self):
""" Call this function to make the tank flash. """
#TODO: White layer opacity 50% ?
1
The simplest solution is to draw the object Tank only in every 2nd frame as long as self.immune is set:
class Tank(GamePhysicsObject):
def __init__(self):
self.immune = False
self.frame_count = 0
def draw(self)
draw_tank = True
if self.immune:
self.frame_count += 1
draw_tank = self.frameCount % 2 == 0
if draw_tank:
# draw the tank
Related
as discussed in the title I am having issues with masking identical images.
#initalising the masks
Invader1= pygame.image.load('Space_invaders_character_1_1.png').convert_alpha()
Invader1= pygame.transform.scale(Invader11, (40,30))
Invader1_mask = pygame.mask.from_surface(Invader11)
Invader1_mask= Invader11_mask.scale((70,40))
Invader2= pygame.image.load('Space_invaders_character_2_1.png').convert_alpha()
Invader2= pygame.transform.scale(Invader21, (40,30))
Invader2_mask = pygame.mask.from_surface(Invader21)
Invader2_mask= Invader11_mask.scale((70,40))
Invader3= pygame.image.load('Space_invaders_character_3_1.png').convert_alpha()
Invader3= pygame.transform.scale(Invader31, (40,30))
Invader3_mask = pygame.mask.from_surface(Invader31)
Invader3_mask= Invader11_mask.scale((70,40))
#drawing characters
def drawEnemies (invX,invY):
for num in range (1,11):
invX = invX + 50
gameDisplay.blit(Invader32, (invX,invY))
gameDisplay.blit(Invader32, (invX,invY-50))
gameDisplay.blit(Invader22, (invX,invY-100))
gameDisplay.blit(Invader22, (invX,invY-150))
gameDisplay.blit(Invader12, (invX, invY -200))
while lives > 0:
offset = (bulletX -invX, bulletY - invY)
result = Invader11_mask.overlap(bullet_mask, offset)
Of course this isn't all my code, however, I hope you see what I am attempting to do. In essence I am attempting to loop to create a specific Invader (yes from space invaders), however, the masks are either not being created with the other invaders or aren't moving. Can someone please help me?
Thanks.
The meaningful answer to your problem is to stop what your doing right now and start using the Sprite and Group classes together with the collide_mask function.
You don't want to create several global variables for each thingy in your game. You want instances of classes (you usually use Sprite), and add them to a list (usually a Group).
So, create a class for your invaders that inherits from Sprite and give them a mask attribue, something like this:
class Invader(pygame.spriteSprite):
def __init__(self, image, pos):
super().__init__()
self.image = image
self.rect = image.get_rect(topleft=pos)
self.mask = pygame.mask.from_surface(image)
def update(self):
pass # handle movement
Create a Group for your bullets and one for your invaders, then you can check the collision with:
pygame.sprite.groupcollide(bullets, invaders, True, True, pygame.sprite.collide_mask)
I've been putting together an isometric tile-based RPG using Python and the Pyglet library. I've run into the following problem, however:
My player movement is based on positions on the three-dimensional array that consists of tiles. To limit movement speed, I use a global variable: TILE_TO_TILE_DELAY = 200.
TILE_TO_TILE_DELAY is supposed to be the amount of time in milliseconds it takes for the player to move from one tile to another. During this time, they should not be able to make a new movement.
The system I've been using is that I have a timer class, like this:
import time
def GetSystemTimeInMS(): #Return system time in milliseconds
systime = round((time.clock()*1000), 0)
return systime
class Timer:
def __init__(self,time):
#time = time for which the timer runs
self.time = time
self.start_time = 0
self.stop_time = 0
def StartTimer(self):
#start_time = time at which the timer was started
self.start_time = GetSystemTimeInMS()
self.stop_time = self.start_time + self.time
def TimerIsStopped(self):
if GetSystemTimeInMS() >= self.stop_time:
return True
else:
return False
The player class has a Timer object:
self.MoveTimer = Timer(TILE_TO_TILE_DELAY)
When the player presses the W-key, a function is called that checks for player.MoveTimer.TimerIsStopped(). If it returns True, it calls player.MoveTimer.StartTimer() and starts a new movement to the next position.
In the even loop, the update(dt) function is set to happen 30 times a second:
def update(dt):
if player.MoveTimer.TimerIsStopped()
player.UpdatePosition()
pyglet.clock.schedule_interval(update, 1/30)
Now, by my logic, update(dt) should check 30 times a second whether or not enough time has passed to warrant the player a new movement event. However, for some reason the player moves much faster at lower FPS.
When my FPS is around 30, the player moves much faster than in areas where there are less tile sprites, pumping the framerate to 60. In areas where FPS is high, the player indeed by my measurements moves almost twice as slowly.
I just cannot figure it out, nor did I find anything off the internet after a day of searching. Some help would be much appreciated.
Edit: The code that starts the MoveTimer:
def StartMovement(self, new_next_pos):
self.RequestedMovement = False
if self.GetCanMoveAgain():
self.SetNextPos(new_next_pos)
self.SetMoveDirection(self.GetDirection())
#Start the timer for the movement
self.MoveTimer.StartTimer()
self.SetIsMoving(True)
self.SetStartedMoving(True)
self.SetWalking(True)
self.SetNeedUpdate(True)
self.MOVE_EVENT_HANDLER.CreateMoveEvent()
GetCanMoveAgain() returns the value of player.can_move_again, which is set back to True by the UpdatePosition() in update(dt)
Alright, I fixed the problem, how ever I'm still unsure about what caused it. Maybe it was some sort of a rounding error with milliseconds, having to do with more checks being made to the clock when the FPS is higher. Anyways, the solution:
Instead of using the system clock, I decided to use Pyglet's own "dt" argument in their update functions:
The dt parameter gives the number of seconds (due to latency, load and timer inprecision, this might be slightly more or less than the requested interval).
The new timer looks like this:
class dtTimer: #A timer based on the dt-paremeter of the pyglet event loop
def __init__(self,time):
self.time = time
self.time_passed = 0
def StartTimer(self):
self.time_passed = 0
def UpdateTimer(self, dt):
self.time_passed += dt*1000
def GetTime(self):
return self.time
def GetTimePassed(self):
if not self.TimerIsStopped():
return self.time_passed
else:
return 0
def TimerIsStopped(self):
if self.time_passed > self.time:
return True
else:
return False
When the loop attempts to update the player's position, if the TimerIsStopped returns false, dt is simply added to the Timer's self.time_passed. This has fixed the issue: the time it takes for the player to move is now constant.
Thanks for looking at my issue.
So i have this platforming game that im programming in pygame. I have a jump method and i want to add a dely between jumps. That way i dont keep jumping into the ceiling. Heres the code
def jump(self):
if (self.onGround == True):
return
self.velocity = 10
self.onGround = False
This is what calls it in the main game loop:
if (event.key==pygame.K_UP):
player.jump()
Keep track of the last time you jumped, or of the next time you're allowed to jump (with pygame.time):
def __init__(self, …):
# ...
self.next_jump = 0
def jump(self):
if pygame.time.get_ticks() < self.next_jump:
return
# ...
self.next_jump = pygame.time.getticks() + 500
Depending on which type of loop you've gone with (traditional event loop, frame-rate-limited loop, unlimited-frame-rate loop) there may be better answers involving, e.g., setting a timer or using a Clock, but this will work with any type.
My intention is to have two music tracks, which are similar in nature, fade between each other at various times. When such a fade occurs, one music track should fade from full volume to muted in a short period of time, and, simultaneously, the other track should fade from 0 to 100 and continue playing from the same time index. They must be able to do this dynamically at any time - when a certain action occurs, the fade will occur and the new track will start playing at the same position that the other one left off at.
This might be plausible by either using volume manipulation or by starting and stopping the music (however, it appears that only a "fadeout" option exists, and there is a lack of a "fadein" option). How can I do this? What is the best method, if any, that exists? If it is impossible using Pygame, alternatives to Pygame are acceptable.
This isn't exactly an answer to the question, but for future-googlers I wrote a script to fade-in my music from volume 0 in the morning and this is what I used:
max_volume = 40
current_volume = 0
# set the volume to the given percent using amixer
def set_volume_to(percent):
subprocess.call(["amixer", "-D", "pulse", "sset", "Master",
str(percent) + "%", "stdout=devnull"])
# play the song and fade in the song to the max_volume
def play_song(song_file):
global current_volume
print("Song starting: " + song_file)
pygame.mixer.music.load(song_file)
pygame.mixer.music.play()
# gradually increase volume to max
while pygame.mixer.music.get_busy():
if current_volume < max_volume:
set_volume_to(current_volume)
current_volume += 1
pygame.time.Clock().tick(1)
play_song("foo.mp3")
Try this, it's pretty straight forward..
import pygame
pygame.mixer.init()
pygame.init()
# Maybe you can subclass the pygame.mixer.Sound and
# add the methods below to it..
class Fader(object):
instances = []
def __init__(self, fname):
super(Fader, self).__init__()
assert isinstance(fname, basestring)
self.sound = pygame.mixer.Sound(fname)
self.increment = 0.01 # tweak for speed of effect!!
self.next_vol = 1 # fade to 100 on start
Fader.instances.append(self)
def fade_to(self, new_vol):
# you could change the increment here based on something..
self.next_vol = new_vol
#classmethod
def update(cls):
for inst in cls.instances:
curr_volume = inst.sound.get_volume()
# print inst, curr_volume, inst.next_vol
if inst.next_vol > curr_volume:
inst.sound.set_volume(curr_volume + inst.increment)
elif inst.next_vol < curr_volume:
inst.sound.set_volume(curr_volume - inst.increment)
sound1 = Fader("1.wav")
sound2 = Fader("2.wav")
sound1.sound.play()
sound2.sound.play()
sound2.sound.set_volume(0)
# fading..
sound1.fade_to(0)
sound2.fade_to(1)
while True:
Fader.update() # a call that will update all the faders..
Pseudocode:
track1 = ...
track2 = ...
track1.play_forever()
track1.volume = 100
track2.play_forever()
track2.volume = 0
playing = track1
tracks = [track1, track2]
def volume_switcher():
while True:
playing.volume = min(playing.volume + 1, 100)
for track in tracks:
if track != playing:
track.volume = max(track.volume - 1, 100)
time.sleep(0.1)
Thread(target=volume_switcher).start()
So it looks like what you want to do in pygame is create two 'Sound' objects, and create a linear interpolation on the volume between the two of them.
I would create two vectors, each from [0,100], and relate them inversely with some constant.
So when sound A is at 100, sound b is at 0. Then when an action occurs, you modify the constant.
t=0
A: [0 ... 100]
B: [0 ... 100]
t=1
ACTION
t=1.1
A:[0 .. 50 .. 100]
B:[0 .. 50 .. 100]
t=2
A:[0 ... 100]
B:[0 ... 100]
Now some code. I'm not familiar with pygame, but this should put you on the right track.
class Song(object):
def __init__(self, songfilename):
self.song = pygame.mixer.Sound(songfilename)
def setVolume(self, somenumber):
#number validation
#possibly do some volume curve here if you wanted
self.song.set_volume(somenumber)
class SongFader(object):
def __init__(self, song1, song2):
self.song1 = song1
self.song2 = song2
self.__xAxisMax = 100
self.__xAxisMin = 0
def fade(self, xaxis):
assert(self.__xAxisMin <= xaxis <= self.__xAxisMax)
#could be any numbers you want.
#i chose 0-100 for convenience
self.song1.setVolume(xaxis)
self.song2.setVolume(self.__xAxisMax-xaxis)
song1 = Song('Song1.wav')
song2 = Song('Song2.wav')
fader = SongFader(song1, song2)
#Inside some event loop when you action is triggered
fader.fade(100) #Only song2 is playing
fader.fade(50) #Songs are evenly split
fader.fade(0) #Only left song is playing
edit
The linear interpolation is probably the more important concept here, so i have modified the fader class, with inspiration from Eric 's thread idea.
class SongFader(object):
def __init__(self, song1, song2):
self.song1 = song1
self.song2 = song2
self.lefttoright = False
self.starttime = 0
self.endtime = 0
def fade(self, starttime, fadeleft):
self.lefttoright = fadeleft == True #Being verbose here
self.starttime = starttime #assuming time is in millis
self.endtime = starttime + 1000
Thread(target = self.fadeHelper).start()
#this is where you define how the two songs are faded
def fadeHelper(self):
#if using thread, make sure you mutex the 'self.' variables
starttime = self.starttime
endtime = self.endtime
lefttoright = self.lefttoright
while starttime < endtime:
fadevalue = (starttime - endtime) / 1000 #a val between [0,1]
if lefttoright:
self.song1.setVolume(fadevalue)
self.song2.setVolume(1-fadevalue)
else:
self.song1.setVolume(1-fadevalue)
self.song2.setVolume(fadefalue)
starttime = getGameTimeFromSomewhere()
I have a ball generator, that "generates" and adds balls(circles) to the simulation.
The ball is to be removed when it hits a static poly in list s_boxes.
This is done by a collision handler ball_wall_collision.
The Error:
The following pop-up window does what it's name says, it pops-up
My code:
Ball Generator
class BallGenerator:
def __init__(self, min_y, max_y, x):
self.min = min_y
self.max = max_y
self.x = x
self.counter = 0
def bowl(self, balls):
global ball_bowled
y = random.randint(self.min, self.max)
pos = to_pymunk((self.x,y))
r = 10
m = 15
i = pm.moment_for_circle(m, 0, r)
b = pm.Body(m,i)
b.position = pos
f_x = random.randint(-600000,-400000)
b.apply_force( (f_x,0.0),(0,0) )
ball = pm.Circle(b, r)
ball.elasticity = 0.75
ball.friction = 0.95
balls.append(ball)
space.add(ball,b)
print 'bowled'
ball_bowled += 1
def handle(self, balls):
if self.counter == FPS:
self.bowl(balls)
self.counter = 0
self.counter += 1
Collision handler
def ball_wall_collision(space, arb, balls, s_boxes):
shapes = arb.shapes
boxes = [box[0] for box in s_boxes] # Get walls
ball = None
wall = None
for ba in balls:
if ba in shapes:
ball = ba
break
for box in boxes:
if box in shapes:
wall = box
break
if wall and ball:
print 'removing'
space.remove(ball, ball.body) # Where the runtime problem happens
balls.remove(ball)
print 'removed'
return False
else:
return True
space.add_collision_handler(0,0,begin=ball_wall_collision,
balls=balls,s_boxes=s_boxes) # Other args to function
What am I doing wrong in the collision handling??
Am I missing something in the call to space.remove?
Is the function not working as I want it to??
Or is the error elsewhere (which I don't think it is)...
It looks like the problem is that you try to remove objects from the space in a collision handler during the simulation step.
Instead you can try with either manually collect all the balls into a list and then call remove after the step, or queue up removes with the post step callback like this:
space.add_post_step_callback(space.remove, ball)
space.add_post_step_callback(space.remove, ball.body)
(untested code)
I should probably try and make this more obvious in the API docs.. I wonder if it would be a good idea to automatically schedule the remove until end of step, or the less intrusive option, trigger a assert in python so you dont get the c++ error.