1

I am creating a basic 2d overhead view game, and am currently working to make the character "face" the mouse as I move it.

Here is my code so far:

class Player(pygame.sprite.Sprite):
    def __init__(self,x,y):
        super(Player,self).__init__()
        self.image=player_image
        self.original_image=self.image
        self.rect=self.image.get_rect(center=(x,y))
        self.change_x=0
        self.change_y=0
        self.speed=7.5
        self.inventory={'wood':0}

    def changespeed(self,x,y):
        self.change_x+=x
        self.change_y+=y

    def update(self,rect):
        player.rotate()
        self.rect.x+=self.change_x
        self.rect.y+=self.change_y
        screen.blit(self.image,(rect.x,rect.y))

    def check(self):
        global tick
        if pygame.mouse.get_pressed()[0]: 
            for tree in resources:
                tree_rect=tree.rect                                
                if self.rect.colliderect(tree_rect) and tick>=9:
                    self.inventory['wood']+=1
                    tick=0

    def rotate(self):
        mouse_x, mouse_y = pygame.mouse.get_pos()
        rel_x, rel_y = mouse_x - self.rect.x, mouse_y -self.rect.y
        angle = (180 / math.pi) * -math.atan2(rel_y, rel_x)
        self.image = pygame.transform.rotate(self.original_image, int(angle))
        self.rect = self.image.get_rect(center=(self.rect.x,self.rect.y))

To do this, I followed this answer. It supposedly calculated the vector between the mouse and the player, and turned the character around toward the mouse each step, but for me the sprite wobbled around when the mouse moved, and turned invisible.

Full code:

    import pygame
from pygame.locals import * 
import sys
import math
import pygame.gfxdraw
import random

pygame.init()

black=(0,0,0)
white=(255,255,255)
forest=(34,139,34)
red=(255,0,0)
blue=(0,0,255)
green=(0,255,0)
light_green=(0,120,0)
dark_green=(0,90,0)
skin=(255,224,189)
yellow=(255,255,0)

wood_image=pygame.image.load('./assets/images/wood.png')
stone_image=pygame.image.load('./assets/images/stone.png')
player_image=pygame.image.load('./assets/images/player.png')

tick=0

def terminate():
    pygame.quit()
    sys.exit()

def drawTextcenter(text,font,screen,x,y,color):
    textobj=font.render(text,True,color)
    textrect=textobj.get_rect(center=(x,y))
    screen.blit(textobj,textrect)

def drawText(text, font, surface, x, y,color):
    textobj=font.render(text, 1, color)
    textrect=textobj.get_rect()
    textrect.topleft=(x, y)
    surface.blit(textobj, textrect)

class Button(object):
    global screen_width,screen_height,screen
    def __init__(self,x,y,width,height,text_color,background_color,text):
        self.rect=pygame.Rect(x,y,width,height)
        self.x=x
        self.y=y
        self.width=width
        self.height=height
        self.text=text
        self.text_color=text_color
        self.background_color=background_color
        self.angle=0

    def check(self):
        return self.rect.collidepoint(pygame.mouse.get_pos())

    def draw(self):
        pygame.draw.rect(screen, self.background_color,(self.rect),0)
        drawTextcenter(self.text,font,screen,self.x+self.width/2,self.y+self.height/2,self.text_color)  
        pygame.draw.rect(screen,self.text_color,self.rect,3) 

class Bar(object):
    def __init__(self,x,y,length,color):
        self.rect=pygame.Rect(x,y,length,17.5)
        self.image=pygame.draw.rect(screen,white,(self.rect),)
        self.x=x
        self.y=y
        self.width=100
        self.height=17.5
        self.color=color
        self.multiplier=length/100

    def draw(self,num): 
        rect=pygame.Rect(self.x,self.y,num*self.multiplier,self.height)
        pygame.draw.rect(screen,self.color,self.rect,3)
        pygame.draw.rect(screen,self.color,rect,0)

class Player(pygame.sprite.Sprite):
    def __init__(self,x,y):
        super(Player,self).__init__()
        self.image=player_image
        self.original_image=self.image
        self.rect=self.image.get_rect(center=(x,y))
        self.change_x=0
        self.change_y=0
        self.speed=7.5
        self.inventory={'wood':0}

    def changespeed(self,x,y):
        self.change_x+=x
        self.change_y+=y

    def update(self,rect):
        self.rotate(camera)
        self.rect.x += self.change_x
        self.rect.y += self.change_y
        rect = camera.apply(self)
        screen.blit(self.image, rect)

    def check(self):
        global tick
        if pygame.mouse.get_pressed()[0]: 
            for tree in resources:
                tree_rect=tree.rect                                
                if self.rect.colliderect(tree_rect) and tick>=9:
                    self.inventory['wood']+=1
                    tick=0

    def rotate(self,camera):
        mouse_x, mouse_y = pygame.mouse.get_pos()
        mouse_x -= camera.state.x
        mouse_y -= camera.state.y
        rel_x, rel_y = mouse_x - self.rect.centerx, mouse_y -self.rect.centery
        angle = -math.degrees(math.atan2(rel_y, rel_x))
        self.image = pygame.transform.rotate(self.original_image, angle)
        self.rect = self.image.get_rect(center=self.rect.center)

class Tree(pygame.sprite.Sprite):
    def __init__(self,x,y):
        super(Tree,self).__init__()
        self.rect=pygame.Rect(x,y,100,100)
        self.rect.x=x
        self.rect.y=y

    def update(self,rect):
        pygame.gfxdraw.filled_circle(screen,rect.x,rect.y,80,light_green)
        pygame.gfxdraw.aacircle(screen,rect.x,rect.y,80,light_green)

class Rock(pygame.sprite.Sprite):
    def __init__(self):
        pass

class Camera(object):
    def __init__(self,camera_func,width,height):
        self.camera_func=camera_func
        self.state=pygame.Rect(0,0,width,height)

    def apply(self,target):
        return target.rect.move(self.state.topleft)

    def update(self,target):
        self.state=self.camera_func(self.state,target.rect)

def complex_camera(camera, target_rect):
    l, t = target_rect.center
    _,_,w,h = camera
    l,t,_,_ = -l+screen_width/2, -t+screen_height/2, w, h

    l = min(0, l)                           
    l = max(-(camera.width-screen_width), l)
    t = max(-(camera.height-screen_height), t)
    t = min(0, t)                           

    return pygame.Rect(l, t, w, h)

clock=pygame.time.Clock()
font=pygame.font.SysFont(None,40)

screen_width=1440
screen_height=800
screen=pygame.display.set_mode([screen_width,screen_height])
pygame.display.set_caption('Survival')

total_level_width=screen_width*5
total_level_height=screen_height*5
camera = Camera(complex_camera, total_level_width, total_level_height)

player=Player(random.randint(100,7900),random.randint(100,5900))
friendlies=pygame.sprite.Group()
friendlies.add(player)

player_health_bar=Bar(125,15,200,green)
player_health=100
player_food_bar=Bar(437.5,15,200,red)
player_food=100
player_thirst_bar=Bar(750,15,200,blue)
player_thirst=100
player_energy_bar=Bar(1100,15,200,yellow)
player_energy=100

resources=pygame.sprite.Group()

wood=False
inventory={}

for i in range(1000):
    tree=Tree(random.randint(100,7900),random.randint(100,5900))
    resources.add(tree)

done1=False
while not done1:
    screen.fill(black)
    font=pygame.font.SysFont(None, 90)
    text_width,text_height=font.size('Survival')
    drawText('Survival', font, screen, (screen_width/2-text_width/2), (screen_height / 2-375),white)
    font=pygame.font.SysFont(None, 40)
    start_button=Button(screen_width/2-125,650,250,50,white,black,'Start')
    start_button.draw()
    back_button=Button(screen_width/2-125,725,250,50,white,black,'Back')
    back_button.draw()
    pygame.display.flip()
    done2=False
    while not done2:
        for event in pygame.event.get():
            if event.type==QUIT:
                terminate()
            elif event.type==pygame.MOUSEBUTTONDOWN:
                if start_button.check()==True: 
                    done3=False
                    pause_back=False
                    while not done3:             
                        tick+=1
                        for event in pygame.event.get():
                            if event.type==pygame.QUIT:
                                terminate()
                            elif event.type==pygame.KEYDOWN:
                                if event.key==pygame.K_a:
                                    player.changespeed(-(player.speed), 0)
                                elif event.key==pygame.K_d:
                                    player.changespeed(player.speed, 0)
                                elif event.key==pygame.K_w:
                                    player.changespeed(0, -(player.speed))
                                elif event.key==pygame.K_s:
                                    player.changespeed(0, player.speed)
                                elif event.key==pygame.K_p:
                                    font=pygame.font.SysFont(None, 90)
                                    text_width,text_height=font.size('Paused')
                                    drawText('Paused', font, screen, (screen_width / 2-(text_width/2)), (screen_height / 2-375),white)
                                    resume_button=Button(screen_width/2-125,650,250,50,white,black,'Resume')
                                    resume_button.draw()
                                    back_button.draw()
                                    pygame.display.flip()
                                    back=False
                                    while not back:
                                        for event in pygame.event.get():
                                            if event.type==QUIT:
                                                terminate()
                                            elif event.type==pygame.MOUSEBUTTONDOWN:
                                                if resume_button.check()==True:
                                                    back=True
                                                elif back_button.check()==True:
                                                    done3=True
                                                    done2=True
                                                    pause_back=True
                                                    back=True

                            elif event.type == pygame.KEYUP:
                                if event.key == pygame.K_a:
                                    player.changespeed(player.speed, 0)
                                elif event.key == pygame.K_d:
                                    player.changespeed(-(player.speed), 0)
                                elif event.key == pygame.K_w:
                                    player.changespeed(0, player.speed)
                                elif event.key == pygame.K_s:
                                    player.changespeed(0, -(player.speed))   

                        camera.update(player)

                        if player.rect.x<0:
                            player.rect.x=0
                        if player.rect.right>total_level_width:
                            player.rect.right=total_level_width
                        if player.rect.y<0:
                            player.rect.y=0
                        if player.rect.bottom>total_level_height:
                            player.rect.bottom=total_level_height

                        screen.fill(dark_green)

                        player.update(camera)

                        for resource in resources:
                            resource.update(camera.apply(resource))

                        player_health_bar.draw(player_health)
                        player_food_bar.draw(player_food)
                        player_thirst_bar.draw(player_thirst)
                        #player_endergy_bar.draw(player_energy)
                        font=pygame.font.SysFont(None, 40)
                        drawText('Health:',font,screen,20,10,green)
                        drawText('Food:',font,screen,350,10,red)
                        drawText('Thirst:',font,screen,650,10,blue)
                        #drawText('Energy:', font, screen, 975, 10, yellow)
                        pygame.draw.rect(screen,forest,(50,675,100,100))
                        pygame.draw.rect(screen,forest,(175,675,100,100))
                        pygame.draw.rect(screen,forest,(300,675,100,100))

                        #player.check()

                        for item in player.inventory:
                            if item=='wood' and player.inventory['wood']>0:
                                wood=True

                        if wood:
                            screen.blit(wood_image,(62.5,687.5))
                            drawTextcenter(str(player.inventory['wood']),font,screen,100,735,black)

                        pygame.display.flip()

                        clock.tick(100)

                        if pause_back==True:
                                break
                    if pause_back==True:
                        break
                    screen.fill(black)
                    font=pygame.font.SysFont(None, 90)
                    text_width,text_height=font.size("Game Over")
                    drawText('Game Over', font, screen, (screen_width/2-text_width/2), (screen_height / 2-200),white)
                    font=pygame.font.SysFont(None, 40)
                    retry_button=Button(screen_width/2-125,650,250,50,white,black,'Retry')
                    retry_button.draw()
                    back_button.draw()
                    pygame.display.flip()
                    back=False 
                    while not back:
                        for event in pygame.event.get():
                                if event.type==QUIT:
                                    terminate()
                                elif event.type==pygame.MOUSEBUTTONDOWN:
                                    if retry_button.check()==True:
                                        back=True
                                    if back_button.check()==True:
                                        back=True
                                        done2=True
                                        done3=True        
                elif back_button.check()==True: 
                    done2=True
                    done1=True

Player.png:

gnawydna
  • 385
  • 1
  • 5
  • 22

1 Answers1

1

I haven't checked out your complete program because it's too much code, but I can see a mistake in the Player class. You're assigning the (self.rect.x,self.rect.y) (the top left) coordinates to the center of the new rect in the rotate method, but you have to assign the center coords of the previous rect.

self.rect = self.image.get_rect(center=(self.rect.x,self.rect.y))

Change the line above to:

self.rect = self.image.get_rect(center=self.rect.center)

Here's a minimal, complete example:

import math
import pygame as pg


class Player(pg.sprite.Sprite):
    def __init__(self, pos):
        super(Player,self).__init__()
        x, y = pos
        self.image = pg.Surface((50, 30), pg.SRCALPHA)
        pg.draw.polygon(
            self.image,
            pg.Color('dodgerblue1'),
            ((1, 1), (49, 15), (1, 29)))
        self.original_image = self.image
        self.rect=self.image.get_rect(center=(x,y))
        self.change_x=0
        self.change_y=0
        self.speed=7.5
        self.inventory={'wood':0}

    def changespeed(self,x,y):
        self.change_x+=x
        self.change_y+=y

    def update(self):
        self.rotate()
        self.rect.x+=self.change_x
        self.rect.y+=self.change_y

    def rotate(self):
        mouse_x, mouse_y = pg.mouse.get_pos()
        rel_x, rel_y = mouse_x - self.rect.x, mouse_y -self.rect.y
        angle = (180 / math.pi) * -math.atan2(rel_y, rel_x)
        self.image = pg.transform.rotate(self.original_image, angle)
        # rect.x and .y are the topleft coords, but you need the to
        # pass the center coordinates of the previous rect to the new one.
        self.rect = self.image.get_rect(center=self.rect.center)


def main():
    screen = pg.display.set_mode((640, 480))
    clock = pg.time.Clock()
    all_sprites = pg.sprite.Group()
    player = Player((300, 200))
    all_sprites.add(player)

    done = False
    while not done:
        for event in pg.event.get():
            if event.type == pg.QUIT:
                done = True
            elif event.type == pg.KEYDOWN:
                if event.key == pg.K_d:
                    player.change_x = 4
                elif event.key == pg.K_a:
                    player.change_x = -4
            elif event.type == pg.KEYUP:
                if event.key == pg.K_d:
                    player.change_x = 0
                elif event.key == pg.K_a:
                    player.change_x = 0

        all_sprites.update()
        screen.fill((30, 30, 30))
        all_sprites.draw(screen)

        pg.display.flip()
        clock.tick(30)


if __name__ == '__main__':
    pg.init()
    main()
    pg.quit()

The problem is caused by the camera. You have to apply the camera offset to the mouse position as well.

Here's a simple solution. First, pass the camera instead of the rect to the player.update method.

player.update(camera)

Pass it to the rotate method as well.

def update(self, camera):
    self.rotate(camera)
    self.rect.x += self.change_x
    self.rect.y += self.change_y
    rect = camera.apply(self)
    screen.blit(self.image, rect)

In the rotate method you have to subtract the camera position from the mouse position to get the world coordinates.

def rotate(self, camera):
    mouse_x, mouse_y = pygame.mouse.get_pos()
    # Subtract the camera offset from the mouse position.
    mouse_x -= camera.state.x
    mouse_y -= camera.state.y
    # Use the center coordinates of the rect.
    rel_x, rel_y = mouse_x - self.rect.centerx, mouse_y -self.rect.centery
    angle = -math.degrees(math.atan2(rel_y, rel_x))
    self.image = pygame.transform.rotate(self.original_image, angle)
    # Set the new center to the previous center coords.
    self.rect = self.image.get_rect(center=self.rect.center)

I also had to modify the complex_camera function, because it should work with the center coordinates.

def complex_camera(camera, target_rect):
    # The camera works with the center instead of
    # the topleft coordinates now.
    l, t = target_rect.center
    _,_,w,h = camera
    l,t,_,_ = -l+screen_width/2, -t+screen_height/2, w, h

    l = min(0, l)                           
    l = max(-(camera.width-screen_width), l)
    t = max(-(camera.height-screen_height), t)
    t = min(0, t)                           

    return pygame.Rect(l, t, w, h)
skrx
  • 19,980
  • 5
  • 34
  • 48
  • Let me know if this solved the problem or if anything else is wrong. – skrx May 04 '18 at 06:56
  • The character still wobbles around. – gnawydna May 04 '18 at 14:05
  • I'd have to analyze the whole program then, but that will take some time. – skrx May 04 '18 at 18:25
  • The problem is caused by the camera. You have to apply the camera offset to the mouse position as well. I'm checking out if I can find an easy way to do that. – skrx May 04 '18 at 19:25
  • I've posted a solution. BTW, there are some things that I'd refactor. For example you shouldn't blit the player image in the `update` method and for the scenes I'd use a [finite state machine like this](https://www.reddit.com/r/pygame/comments/3kghhj/simple_state_machine_example). – skrx May 04 '18 at 20:19
  • It appears now but still wobbles around. – gnawydna May 04 '18 at 21:12
  • Have you modified everything that I mentioned? It works correctly for me now. – skrx May 04 '18 at 21:44
  • Let me post what I changed. – gnawydna May 04 '18 at 22:14
  • I don't see any wobbling anymore (only if I'm at the edge of the map and press the movement keys). I've got no idea why it doesn't work for you. – skrx May 04 '18 at 22:31
  • I'm still wondering why it doesn't work for you, especially since we're using the same code (except for the images). Perhaps the image has something to do with the problem, but that would be really odd. Could you add the player.png image to the question? – skrx May 05 '18 at 05:00
  • It is is all fixed. The image wasn't cropped. – gnawydna May 05 '18 at 13:45
  • Ah, of course! I hadn't thought of that, but had the vague feeling that something was wrong with the image. Have fun with your project! – skrx May 05 '18 at 18:42
  • Whenever I rotate the player image the quality decreases. Is their a way to solve this. – gnawydna May 06 '18 at 23:27
  • That should actually not happen because you still have the original image (`self.original_image`). When you rotate it in the `rotate` method with `pygame.transform.rotate`, the original image is not modified but a new surface is created which you then assign to the `self.image` attribute. Maybe you mean the "pixelated" look of the surfaces that `pygame.transform.rotate` returns. I usually prefer to rotate images with the [pygame.transform.rotozoom](http://www.pygame.org/docs/ref/transform.html#pygame.transform.rotozoom) function which returns smoother surfaces. – skrx May 06 '18 at 23:49