Python Forum
[PyGame] Rotating image issue
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
[PyGame] Rotating image issue
#1
Hello everyone! I'm a new pygame/python (wannabe) programmer, and I stepped into a problem.

I want to have a character displayed on the screen, being able to move in every direction. When I left-click on it, he is "selectionned"(yes I'm aware of the error). When selected, if I right-click somewhere else, it will move to that position, and stop once destination reached. Until there, no problem at all, everything is working as intended.

However, I want to add the possibility of my character rotating to the direction where it is going. While the rotation is working fine, the problem is -and as I saw through my internet research, a common one- that the image is actually being displaced. The reason for it, I know it very well: the "rect" cannot be circular, so the image is moving, to look like a square on its tip. As a square diagonal is bigger than its side, the image is moving to the bottom right of the screen, the worst being when you're at a 45° angle. Here is a gif to make it clearer:
[Image: 7GfVA.gif]
While i would like something like:
[Image: diqBY.gif]
Note that I don't mind the hitbox changing of size, but if possible I wouldn't be nitpicky.

I've seen many ways to correct it, however, all of them were really out of reach for me, even more as English is not my first language. I see that most people use "pygame.transform.rotate()", however my code is a heavily modified version of a game I previously made with a friend, and I have no idea how to adapt it. Here it is:

import pygame,random,math
from pygame.locals import *

class Hero(pygame.sprite.Sprite):
    
    def __init__(self, x, y,DIRECTION,upKeyPressed,downKeyPressed,leftKeyPressed,rightKeyPressed, leftMousePressed, rightMousePressed, oneKeyPressed, HP, game):
        pygame.sprite.Sprite.__init__(self)
        self.image = self.perso_rotated_surf = pygame.image.load("Sprites/Ant-rot.png").convert_alpha()
        self.step1 = pygame.image.load("Sprites/Ant-1.png").convert_alpha()
        self.step2 = pygame.image.load("Sprites/Ant-mid.png").convert_alpha()
        self.step3 = pygame.image.load("Sprites/Ant-3.png").convert_alpha()
        self.step4 = pygame.image.load("Sprites/Ant-mid.png").convert_alpha()
        self.rect = self.image.get_rect()
        self.step = [self.step1,self.step2,self.step3,self.step4]
        self.perso_angle = 0
        self.ticker = 0
        self.rect.x = x
        self.rect.y = y
        self.center = None
        self.DIRECTION = DIRECTION
        self.upKeyPressed = upKeyPressed
        self.downKeyPressed = downKeyPressed
        self.leftKeyPressed = leftKeyPressed
        self.rightKeyPressed = rightKeyPressed
        self.leftMousePressed = leftMousePressed
        self.rightMousePressed = rightMousePressed
        self.oneKeyPressed = oneKeyPressed
        self.RIGHT, self.LEFT, self.UP, self.DOWN = "right left up down".split()
        self.game = game
        self.current_frame = 0
        self.vitessex = 0
        self.accx = 0
        self.vitessey = 0
        self.accy = 0
        self.coordx = 1920/2
        self.coordy = 1080/2
        self.centerc = None
        self.perso_angle = 0
        self.selection = 0, 0
        self.selectionned = False
        self.xdest = 0
        self.ydest = 0
        self.angledeg = 0


    def centerPos(self):
        self.center = (self.rect.x + 56, self.rect.y + 75)
        self.centerc = (self.coordx, self.coordy)

    def update(self):

        xc, yc = self.center
        xd, yd = self.selection

        if self.leftMousePressed:
            xm, ym = self.selection
            if xm >= (xc - 56) and xm < (xc + 56) and ym >= (yc - 75) and ym < (yc + 75):
                self.selectionned = True
            else:
                self.selectionned = False

        if self.rightMousePressed:
            self.xdest = xd
            self.ydest = yd
            if (xd-xc) >= 0:
                if (yd-yc) == 0:
                    self.vitessey = 0
                    self.vitessex = -5
                if (yd-yc) > 0:
                    angled = (math.atan((xd-xc)/(yd-yc)))
                    self.angledeg = ((angled*180) / 3.1415) + 90 #1
                    if self.selectionned == True:
                        angle2 = (self.angledeg * 3.1415) / 180
                        self.vitessex = -5 * math.cos(angle2)
                        self.vitessey = 5 * math.sin(angle2)
                if (yd-yc) < 0:
                    angled = (math.atan((xd-xc)/(yd-yc)))
                    self.angledeg = ((angled*180) / 3.1415) + 270 #2
                    if self.selectionned == True:
                        angle2 = (self.angledeg * 3.1415) / 180
                        self.vitessex = -5 * math.cos(angle2)
                        self.vitessey = 5 * math.sin(angle2)
            if (xd-xc) < 0:
                if (yd-yc) == 0:
                    self.vitessey = 0
                    self.vitessex = 5
                if (yd-yc) > 0:
                    angled = (math.atan((xd-xc)/(yd-yc)))
                    self.angledeg = ((angled*180) / 3.1415) + 90 #3
                    if self.selectionned == True:
                        angle2 = (self.angledeg * 3.1415) / 180
                        self.vitessex = -5 * math.cos(angle2)
                        self.vitessey = 5 * math.sin(angle2)
                if (yd-yc) < 0:
                    angled = (math.atan((xd-xc)/(yd-yc)))
                    self.angledeg = ((angled*180) / 3.1415) + 270 #4
                    if self.selectionned == True:
                        angle2 = (self.angledeg * 3.1415) / 180
                        self.vitessex = -5 * math.cos(angle2)
                        self.vitessey = 5 * math.sin(angle2)

            if self.vitessey != 0 or self.vitessex != 0:            
                self.perso_angle = (self.angledeg + 90)
                self.perso_rotated_surf = pygame.transform.rotate(self.image, self.perso_angle)
                self.rect = self.perso_rotated_surf.get_rect(center=self.centerc)

        if xc >= (self.xdest - 10) and xc <= (self.xdest + 10) and yc >= (self.ydest - 10) and yc <= (self.ydest + 10):
            self.vitessex = 0
            self.vitessey = 0

        self.ticker += 1
        if self.ticker % 8 == 0:
            self.current_frame = (self.current_frame + 1) % 4

        self.vitessex += self.accx
        self.coordx += self.vitessex
        self.rect.x = self.coordx
        self.accx = 0

        if self.vitessey != 0 or self.vitessex != 0:
            self.image = self.step[self.current_frame]
            self.perso_rotated_surf = pygame.transform.rotate(self.image, self.perso_angle)

        self.vitessey += self.accy
        self.coordy += self.vitessey
        self.rect.y = self.coordy
        self.accy = 0


class Room(object):
    wall_list = None
    def __init__(self):
        self.bullet = pygame.sprite.Group()

class Room1(Room):
    def __init__(self):
        Room.__init__(self)
        self.background = pygame.image.load("room/room1.png").convert_alpha()
        mobs = []

class GameMain():
    done = False
    
    def __init__(self,width = 1920, height = 1080):
        pygame.mixer.pre_init(44100, -16, 2, 2048)
        pygame.init()

        self.arial_font = pygame.font.SysFont("arial", 30)
        self.width, self.height = width, height
        pygame.display.set_caption("Ants")
        self.screen = pygame.display.set_mode((self.width, self.height), pygame.FULLSCREEN)
        self.hero = Hero(100,200,"UP",False,False,False,False,False,False,False,20, self)
        self.all_sprite_list = pygame.sprite.Group()
        self.all_sprite_list.add(self.hero)
        self.rooms = [[Room1()]]
        self.clock = pygame.time.Clock()
        self.current_x = 0
        self.current_y = 0
        self.current_screen = "title"
        self.current_room = self.rooms[self.current_y][self.current_x]
        
    def main_loop(self):
        while not self.done:
            if self.current_screen == "game":
                    self.handle_events()
                    self.hero.centerPos()
                    self.draw()
                    self.all_sprite_list.update()
                    self.change_room()
                    self.current_room.bullet.update()
            elif self.current_screen == "title":
                self.handle_events_title()
                self.draw_title()
            self.clock.tick(60)
        
        pygame.quit()

    def handle_events_title(self):
        events = pygame.event.get()
        for event in events:
            if event.type == pygame.QUIT:
                self.done = True
            if event.type == KEYDOWN:
                if event.key == K_RETURN:
                    self.current_screen = "game"
                if event.key == K_F4 :
                    self.done = True

    def draw_title(self):
        self.screen.fill(Color("Black"))
        credit = pygame.image.load("background_menu.jpg")
        self.screen.blit(credit,(0,0))
        pygame.display.flip()

    def draw(self):
        self.screen.fill((255,255, 255))
        background = self.current_room.background
        self.screen.blit(background,(0,0))
        self.all_sprite_list.draw(self.screen)
        self.screen.blit(self.hero.perso_rotated_surf, self.hero.rect)            
        pygame.display.flip()

    def change_room(self):
        if self.hero.coordx > 1921 :
            self.hero.coordx = 0
            self.current_room = self.rooms[self.current_y][self.current_x]

        elif self.hero.coordx < 0 :
            self.hero.coordx = 1919
            self.current_room = self.rooms[self.current_y][self.current_x]

        elif self.hero.coordy < 0 :
            self.hero.coordy = 1079
            self.current_room = self.rooms[self.current_y][self.current_x]

        elif self.hero.coordy > 1080 :
            self.hero.coordy = 0
            self.current_room = self.rooms[self.current_y][self.current_x]

    def handle_events(self):
        events = pygame.event.get()
        for event in events:
            if event.type == pygame.QUIT:
                self.done = True
            elif event.type == KEYDOWN :
                if event.key == (K_LALT and K_F4):
                    self.done = True

            if event.type == MOUSEBUTTONDOWN :
                if pygame.mouse.get_pressed() == (1, 0, 0):
                    self.hero.leftMousePressed = True

                if pygame.mouse.get_pressed() == (0, 0, 1):
                    self.hero.rightMousePressed = True

            elif event.type == MOUSEMOTION :
                self.hero.selection = event.pos

            elif event.type == MOUSEBUTTONUP :
                if pygame.mouse.get_pressed() == (0, 0, 0):
                    self.hero.leftMousePressed = False

                if pygame.mouse.get_pressed() == (0, 0, 0):
                    self.hero.rightMousePressed = False

if __name__ == "__main__":
    game = GameMain()
    game.main_loop()
So if anyone has an idea how I could fix this, I would be really grateful. Thank you for reading!
Reply
#2
I could take a look at it when I get back to a desk top. Until the it might be a good idea to upload the images or put your code online with the images.
Recommended Tutorials:
Reply
#3
(Oct-11-2019, 09:52 PM)metulburr Wrote: Until the it might be a good idea to upload the images or put your code online with the images.

Sure, I host the project in private on Github if needed to, but aside from that, how could I upload you the code?
Reply
#4
You can upload the 6 images on the forum. But it would be easier to just make your repo public and give us the link. Or imgur.
Here is a tutorial of rotating via center
https://python-forum.io/Thread-PyGame-Common-Tasks

Sorry to be short. It's a pain in the ass on a phone.
Recommended Tutorials:
Reply
#5
Here is the repository, made public. Feel free to fork. The code to use is AntRot2.py

[Link removed by user]

And don't worry, I'm already more than happy to get some help with this.
Reply
#6
Rotate an image in a circle.
1. Store center point.
2. Rotate from original image.
3. Get image new rect.
4. Set rect.center with store center.

Also improve code.
1. Refactor Hero class. Its handling too much.
2. Learn to use pygame rects and vector2.
3. Your math can be made simpler. math.radians(angle) over angle * math.pi / 180
99 percent of computer problems exists between chair and keyboard.
Reply
#7
Quote:1. Store center point.
2. Rotate from original image.
3. Get image new rect.
4. Set rect.center with store center.

huuuh, I'm not so sure to what I have to do exactly.

So in my init let's say I have
        self.master_image = pygame.image.load("Sprites/Ant-rot.png").convert_alpha()
        self.masterrect = self.master_image.get_rect(center=(75,75))
        self.image = self.master_image.copy()
        self.rect = self.image.get_rect(center=(75,75))
and in the update part

        self.image = pygame.transform.rotate(self.master_image, self.perso_angle)
        self.rect = self.image.get_rect(center=self.masterrect.center)
when I do that my character is stuck on the right part of the screen. I'm quite lost on what I exactly have to do.

Quote:Also improve code.

yeah with each commit I try to remove/optimize useless/poorly written parts. If you had seen it when I first imported it from another code and then modified, I think you would have had a stroke ahaha.
Reply
#8
Here an example.
import os
import pygame
from pygame.sprite import Sprite
from math import radians, sin, cos

class Scene:
    def on_draw(self, surface): pass
    def on_event(self, event): pass
    def on_update(self, delta): pass

class Manager:
    def __init__(self, caption, width, height, center=True):
        if center:
            os.environ['SDL_VIDEO_CENTERED'] = '1'

        # Basic pygame setup
        pygame.display.set_caption(caption)
        self.surface = pygame.display.set_mode((width, height))
        self.rect = self.surface.get_rect()
        self.clock = pygame.time.Clock()
        self.running = False
        self.delta = 0
        self.fps = 60

        # Scene Interface
        self.scene = Scene()

    def mainloop(self):
        self.running = True
        while self.running:
            for event in pygame.event.get():
                self.scene.on_event(event)

            self.scene.on_update(self.delta)
            self.scene.on_draw(self.surface)
            pygame.display.flip()
            self.delta = self.clock.tick(self.fps)

class BaseSprite(Sprite):
    def __init__(self, image, position, anchor="topleft"):
        Sprite.__init__(self)
        self.original_image = image
        self.image = image
        self.rect = image.get_rect()
        setattr(self.rect, anchor, position)

    def draw(self, surface):
        surface.blit(self.image, self.rect)

class RotationMovementKeys:
    def __init__(self, sprite, up, down, left, right):
        self.sprite = sprite
        self.up = up
        self.down = down
        self.left = left
        self.right = right

        self.angle = 0
        self.speed = 0.04
        self.rotation_speed = 0.08
        self.center = pygame.Vector2(self.sprite.rect.center)
        self.set_direction()

    def set_direction(self):
        rad = radians(self.angle)
        self.direction = pygame.Vector2(sin(rad), cos(rad))

    def do_rotate(self):
        self.sprite.image = pygame.transform.rotate(self.sprite.original_image, self.angle)
        self.sprite.rect = self.sprite.image.get_rect()
        self.sprite.rect.center = self.center
        self.set_direction()

    def on_keydown(self, keys_press, delta):
        if keys_press[self.up]:
            self.center -= self.direction * delta * self.speed
            self.sprite.rect.center = self.center
        elif keys_press[self.down]:
            self.center += self.direction * delta * (self.speed / 2)
            self.sprite.rect.center = self.center

        if keys_press[self.right]:
            self.angle = (self.angle - self.rotation_speed * delta) % 360
            self.do_rotate()
        elif keys_press[self.left]:
            self.angle = (self.angle + self.rotation_speed * delta) % 360
            self.do_rotate()

class Example(Scene):
    def __init__(self, manager):
        self.create_image()
        self.manager = manager
        self.sprite = BaseSprite(self.rectangle, manager.rect.center, "center")
        self.sprite_movement = RotationMovementKeys(self.sprite,
            pygame.K_w, pygame.K_s, pygame.K_a, pygame.K_d)

    def create_image(self):
        self.rectangle = pygame.Surface((10, 20), pygame.SRCALPHA)
        self.rectangle.fill(pygame.Color('dodgerblue'))
        self.rectangle.fill(pygame.Color('white'), (2, 2, 6, 3))

    def on_draw(self, surface):
        surface.fill(pygame.Color("black"))
        self.sprite.draw(surface)

    def on_event(self, event):
        if event.type == pygame.QUIT:
            self.manager.running = False

    def on_update(self, delta):
        keys = pygame.key.get_pressed()
        self.sprite_movement.on_keydown(keys, delta)

def main():
    pygame.init()
    manager = Manager("Rotation Example", 800, 600)
    manager.scene = Example(manager)
    manager.mainloop()

if __name__ == "__main__":
    main()
99 percent of computer problems exists between chair and keyboard.
Reply
#9
Wow, that's a nice take! I will try to mod it to get back to what I had, and if I achieve it, I will set the thread solved. Thank you a lot!
Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
  Rotating a rectangle CompleteNewb 19 12,975 Aug-28-2021, 03:23 PM
Last Post: CompleteNewb

Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020