Home [데브코스] 4주차 - ROS Develop Pygame
Post
Cancel
Preview Image

[데브코스] 4주차 - ROS Develop Pygame


개발 환경 구축

pygame은 python을 통해 게임을 만드는 도구이다. 이를 설치해서 차량 시뮬레이터를 제작할 것이다.


차량의 움직임을 제어하려면 구동 메커니즘을 이해해야 한다. 이를 위해 ackermann steering을 이해해야 할 것이다. 안쪽 바퀴는 많이, 바깥 바퀴는 덜 꺽일 것이다.


키보드로 차량을 조종할 수도 있다. 차량은 진짜 차처럼 ackermann steering 방식으로 회전하도록 해야 할 것이고, 진짜 차량의 엑셀/브레이크/핸들처럼 동작시켜야 한다.


이를 window + visual studio를 설치하겠다.


라이브러리 설치

라이브러리

  • python==3.7.0
  • pygame==1.9.6
    1
    
    pip install pygame==1.9.6
    
  • pip install pillow
    1
    
    pip install pillow
    
  • numpy
    • numpy사이트로 가서 파이썬 3.7로 다운
      1
      
      python -m pip install .\numpy-1.20.2-cp37m-win_amd64.whl
      

visual studio 설치

자신에 맞는 버전을 다운 받으면 된다.

커뮤니티버전으로 설치하고, c++를 사용한 데스크톱 개발, MSVC v141 - VS 2017 C++ ~ 클릭


Pygame 예제

간단한 집 그리기

https://kkamikoon.tistory.com/129


  1. pygame 선언 (import)
  2. pygame 초기화 (pygame.init())
  3. pygame에서 사용할 전역 변수 선언
    • size : x,y 크기
    • screen : pygame.display.set_mode(size)
    • clock : pygame.time.Clock()
  4. pygame 메인 루프(while)
    • pygame event 설정
    • pygame 화면 설정
    • 사용자 행동


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# pygame_draw_house.py

''' 1. pygame 선언 '''
# Import a library of functions called 'pygame' as pg
import pygame as pg

''' 2. pygame 초기화 '''
# Initialize the game engine
pygame.init() 

''' 3. pygame 전역 번수 선언 '''
# Define the colors we will use in RGB format
BLACK= ( 0,  0,  0)
WHITE= (255,255,255)
BLUE = ( 0,  0,255)
GREEN= ( 0,255,  0)
RED  = (255,  0,  0)
 
# Set the height and width of the screen
size  = [400,300]
screen= pygame.display.set_mode(size)
  
pygame.display.set_caption("Game Title")
  
#Loop until the user clicks the close button.
done= False # while 루프를 빠져나오기 위한 플래그 변수
clock= pygame.time.Clock() # fps 초당 화면 갱신 회수 지정 용도

''' 4. pygame 메인 루프 '''
while not done:  
    # This limits the while loop to a max of 10 times per second.
    # Leave this out and we will use all CPU we can.
    clock.tick(10) # 초당 10번 루프를 돌도록 즉, 초당 10번 갱신, fps가 10
     
    # Main Event Loop
    for eventin pygame.event.get():# User did something
        if event.type == pygame.QUIT:# If user clicked close
            done=True # Flag that we are done so we exit this loop
  
    # All drawing code happens after the for loop and but
    # inside the main while done==False loop.
      
    # Clear the screen and set the screen background
    screen.fill(WHITE)
 
    '''
    Your Work.....
    '''
    pygame.draw.polygon(screen, GREEN, [[30,150], [125,100], [220,150]],5) # (window, color, coordinates, thickness)
    pygame.draw.polygon(screen, GREEN, [[30,150], [125,100], [220,150]],0)
    pygame.draw.lines(screen, RED,False, [[50,150], [50,250], [200,250], [200,150]],5)
    pygame.draw.rect(screen, BLACK, [75,175,75,50],5)
    pygame.draw.rect(screen, BLUE, [75,175,75,50],0) # 0 이면 안을 채운다.
    pygame.draw.line(screen, BLACK, [112,175], [112,225],5)
    pygame.draw.line(screen, BLACK, [75,200], [150,200],5)
 
    # Go ahead and update the screen with what we've drawn.
    # This MUST happen after all the other drawing commands.
    pygame.display.flip()

# Be IDLE friendly
pygame.quit()


코드 실행

1
python pygame_draw_house.py


Pygame 예제 2

키보드 입력을 사용하는 예제

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import pygmae

pygame.init()

size = [400,300]
screen = pygame.display.set_mode(size)
pygaem.display.set_caption("Game Titie")

done = False

clock = pygame.time.Clock()

player_location= [200, 150] # 초기값

speed = 10 #키를 눌렀을 때 몇 픽셀 가는지

while not done:
  clock.tick(30) # fps

  for event in pygame.event.get():
    if event.type == pygame.QUIT:
      done = True # QUIT 이면 루프를 빠져나감

  pressed = pygame.key.get_pressed() # 사용자가 무엇을 눌렀나

  if pressed[pygame.K_UP]:
    player_location[1] -= speed

  elif pressed[pygame.K_DOWN]:
    player_location[1] += speed

  if pressed[pygame.K_RIGHT]:
    player_location[0] += speed
  
  elif pressed[pygame.K_LEFT]:
    player_location[0] -= speed

  screen.fill((255,255,255)) # 흰색

  pygame.draw.circle(screen, (0,0,255), player_location, 40) # RGB, 파란색

  pygame.display.filp()

pygame.quit()



차랑주행 시뮬레이터 설계

시뮬레이터 UI

스크린에 차량을 먼저 표기하고 키보드로 움직이도록 하고자 한다.

처음 셋팅

  1. 저장된 차량 이미지를 표시
  2. 처음 위치 셋팅
  3. 차량 바퀴는 x
  4. 스크린 크기 1280x720
  5. 바탕은 검정


행동 감지

  • 위방향 -> 전진할 때는 가속, 후진할 때는 브레이크
  • 아래방향 -> 전진할 때는 브레이크, 후진할 때는 가속
  • 오른방향 -> 바퀴를 오른쪽으로 꺽음, 최대 꺽임 각도 30도
  • 왼방향 -> 바퀴를 왼쪽으로 꺽음, 최대 꺽임 각도는 -30도
  • 스페이스바는 방향 상관없이 브레이크


좌우 핸들링

  • 좌우 키를 누르고 있으면 바퀴가 조금씩 좌/우로 돌아감
  • 키에서 손을 떼면 바퀴가 즉시 정면으로 되돌아와 정렬된다.

전진

  • 위쪽 키를 누르고 있으면 엑셀을 계속 밟는 것처럼 속도가 점점 빨라진다.
  • 키에서 손을 떼면 엑셀에서 발을 뗀 것처럼 속도가 점차 줄어서 멈춘다
  • 아래 방향을 누르면 브레이크가 걸린 것처럼 속도가 빨리 줄어서 멈춘다.(멈추고도 계속 누르면 후진 가속, 정차시키려면 스페이스바를 누른다)

후진

  • 전과 동
  • 키만 반대



HLD(High Level Design) 설계

  1. Car 클래스 정의 (위치/자세 정보)
  2. pygame 초기화
  3. 윈도우 타이틀 지정 / 크기 설정
  4. 전역 변수 설정 / 클래스 객체 생성
  5. while not exit_flags:
  6. for 루프(pygame.QUIT 이벤트 처리)
  7. 키 입력값 읽어들여서 if/elif/else 블록에서 처리 7-1. if pressed[pygame.K_UP] : if # 선속도가 음수면 , else # 선속도가 양수면
  8. 차량의 새로운 위치/자세 계산하고 그림 새로 그리기
  9. 화면 업데이트


LLD(Low Level Design) 설계

1
class car:

car클래스가 가지고 있어야 할 값들

  • self.x/y : 현재 x,y좌표
  • self.yaw : 진행방향
  • self.brake_deceleration : 브레이크로 인한 감속 가속도
  • self.free_deceleration : 정지마찰력으로 인한 감속 가속도
  • self.linear_accelation : 선가속도
  • self.linear_velocity : 선속도
  • self.steering_angle : 조향각
  • self.wheel_base : 차량의 휠베이스 길이
  • self.car_img_x : 차량 이미지를 둘러싼 사각형의 좌상단점의 x좌표 # 비스듬히 있을 때는 차량에 딱 맞게 비스듬히가 아닌 x,y 좌표와 평행한 큰 사각형으로 정해야 한다.
  • self.car_img_y : 차량 이미지를 둘러싼 사각형의 좌상단점의 y좌표
  • self.car_x_ori : 차량 이미지 4개 꼭지점의 x좌표들
  • self.car_y_ori : 차량 이미지 4개 꼭지점의 y좌표들

  • def update(self, dt):
    • 선속도 계산 with 선가속도 , 시간
    • 각속도 계산 with 선속도 , 휠베이스, 조향각
    • 차량진행방향 계산 with 각속도 , 시간
    • 이동거리 계산 with 선속도 , 시간
    • 새로운 차량위치 계산 with 이동거리 , 차량진행방향
    • 차량 이미지 4개 꼭지점 새로운 위치 계산 with 차량진행방향


회전 변환

선속도 = 이동거리 / 이동시간

각속도 = 이동각도 / 이동시간

선속도 = 회전반지름 x 각속도


회전 반경 r = 휠베이스 L / tanϴ

이 때, 휠베이스는 앞바퀴축과 뒷바퀴축간의 거리, ϴ는 조향각이다. 휩베이스는 항상 정해져있고, ϴ는 키를 계속 누르고 있는 만큼 계속 일정하게 추가되면 된다.


회전 차량을 그릴 때 회전 없이 이동시킨 후 회전시키는 것이 좋다.


각도가 ϴ만큼 회전할 때의 좌표가 (x,y) -> (x’,y’) 로 변한다고 생각을 하면, 이를 X,Y 축으로 분할을 해서 생각을 해본다.

이를 행렬로 나타내면 다음과 같다.

x’ = x * cosϴ - y * sinϴ

y’ = x * sinϴ + y * cosϴ


그림 작도

이처럼 비스듬히 되면 x,y축과 평행한 사각형으로 생성해야 한다. 따라서, (x1”,y1”) 반드시 알아야 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
#!/usr/bin/env python

import os
import pygame
import numpy as np
from math import sin, radians, degrees, copysign

class car:
    # 생성자 함수
    def __init__(self,x,y,yaw=0.0,max_steering=30, max_acceleration=1000.0):
        # initial point(x,y)
        self.x = x
        self.y = y

        # yaw value
        self.yaw = yaw

        # max acceleration
        self.max_acceleration = max_acceleration

        # max steering
        self.max_steering = max_steering

        # down acceleration due to brake (spacebar)
        self.brake_deceleration = 300

        # down acceleration due to static friction force (no press key and only pulling excel
        self.free_deceleration = 50

        # linear acceleration
        self.linear_acceleration = 10.0

        # linear velocity
        self.linear_velocity = 0.0

        # max velocity
        self.max_velocity = 1000

        # steering
        self.steering_angle = 0.0

        # wheel base (축거 : 앞바퀴축과 뒷바퀴축 사이의 거리)
        self.wheel_base = 84

        # car image coordinate (widthxheight = 128x64, car.png)
        self.car_img_x = 0
        self.car_img_y = 0
        self.car_x_ori = [-64,-64,64,64]    # 1     3
        self.car_y_ori = [-32,32,-32,32]    # 2     4

    def update(self, dt): # fps를 주는 것, dt를 이용
        # calculate linear velocity (linear velocity = linear acceleration x dt
        self.linear_velocity += (self.linear_acceleration * dt)

        # limit range of linear velocity between -100 and 100
        self.linear_velocity = min(max(-self.max_velocity, self.linear_velocity), self.max_velocity)

        self.angular_velocity = 0.0

        # steering is not zero
        if self.steering_angle != 0.0:
            # calculate the angular velocity, angular velocity = (linear velocity / radius)
            #                                                  = (linear velocity / wheel base) * tan(thata)
            self.angular_velocity = (self.linear_velocity / self.wheel_base) * np.tan(np.radians(self.steering_angle))

        # calculate angular distance and add to the angle value (angular velocity x time = angular distance)
        # angle of movement = angular velocity * time of movement
        self.yaw += (np.degrees(self.angular_velocity) * dt)

        # distance = linear velocity * time of movement
        self.spatium = self.linear_velocity * dt

        # get the x,y coordinate using rotational transformation matrix
        # distance of movement = linear velocity * time of movement
        self.x += (self.spatium * np.cos(np.radians(-self.yaw)))
        self.y += (self.spatium * np.sin(np.radians(-self.yaw)))

        # storage space for coordinate of upper left rectangle
        car_x = [0,0,0,0]
        car_y = [0,0,0,0]

        for i in range(4):
            # x' = x * cosϴ - y * sinϴ
            # y' = x * sinϴ - y * cosϴ
            # 회전행렬의 각도는 반시계 방향이 +인데, 자동차는 우회전이 +, 좌회전이 -이기 때문에 -를 붙인 것이다.
            car_x[i] = self.car_x_ori[i] * np.cos(-radians(self.yaw)) - self.car_y_ori[i] * np.sin(-radians(self.yaw)) + self.x
            car_y[i] = self.car_x_ori[i] * np.sin(-radians(self.yaw)) - self.car_y_ori[i] * np.cos(-radians(self.yaw)) + self.y

        self.car_img_x = int(round(min(car_x))) # x1"
        self.car_img_y = int(round(min(car_y))) # y1"


pygame.init()

pygame.display.set_caption("Pygame Car Simulator #1")

width, height = 1280, 720

screen = pygame.display.set_mode((width, height))

clock = pygame.time.Clock()

current_dir = os.path.dirname(os.path.abspath(__file__))
image_path = os.path.join(current_dir, "car.png")
car_image = pygame.image.load(image_path)

image_scale = pygame.transform.scale(car_image, (128,64))

pygame.image.save(image_scale, "car.png")

car = car(100,100) # 초기 위치가 (100,100)

exit_flags = False

while not exit_flags:
    clock.tick(60) # 60fps

    dt = clock.get_time() / 1000 # fps는 밀리초로 반환되는데 이걸 1000으로 나누어 초로 변환

    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            exit_flags = True

    pressed = pygame.key.get_pressed()

    # if up_key press
    if pressed[pygame.K_UP]:

        # linear velocity is negative (now backward)
        if car.linear_velocity < 0:
            # apply the brake deceleration
            car.linear_acceleration = car.brake_deceleration

        # linear velocity is positive (now advance)
        else:
            # increase the linear acceleration  10 in the + direction
            car.linear_acceleration += 20 * dt

    # if down_key press
    elif pressed[pygame.K_DOWN]:
        # linear velocity is positive (now advance)
        if car.linear_velocity > 0:
            # apply the brake deceleration
            car.linear_acceleration = -car.brake_deceleration

        # linear velocity is negative (now backward)
        else:
            # increase the linear acceleration by 10 in the - direction
            car.linear_acceleration -= 20 * dt

    # if spacebar press
    elif pressed[pygame.K_SPACE]:
        # it is because if only give subtraction, we make backward acceleration increase
        # linear velocity is more than brake_acceleration * dt
        if abs(car.linear_velocity) > dt * car.brake_deceleration:
            # copysign(double x, double y) ==> use the y sign as the abs(x) sign
            # subtract the brake acceleration at linear acceleration to decrease the linear acceleration
            car.linear_acceleration = -copysign(car.brake_deceleration, car.linear_velocity)

        # linear velocity is less than brake_acceleration * dt
        else:
            # simply, subtract [(linear velocity/dt) = linear acceleration] to make linear velocity be zero
            car.linear_acceleration = -car.linear_velocity / dt

    # if another key press, apply the free friction force at car
    else:
        # linear velocity is more than free_acceleration * dt
        if abs(car.linear_velocity) > dt * car.free_deceleration:
            # apply the free acceleration, so stop
            car.linear_acceleration = -copysign(car.free_deceleration, car.linear_velocity)

        # linear velocity is less than free_acceleration * dt
        else:
            # linear velocity is more than (free deceleration x dt)
            if dt != 0:
                # subtract [(linear velocity / dt) = linear acceleration] from linear acceleration, so acceleration is zero
                car.linear_acceleration = -car.linear_velocity / dt

    # limit the value of range of linear acceleration between -1000.0 and 1000.0
    car.linear_acceleration = max(min(car.linear_acceleration, car.max_acceleration),-car.max_acceleration)

    # if right_key press
    if pressed[pygame.K_RIGHT]:
        # turn right, subtract (30 x dt)
        car.steering_angle -= 30 * dt

    # if left_key press
    elif pressed[pygame.K_LEFT]:
        # turn right, add (30 x dt)
        car.steering_angle += 30 * dt

    # if anything is not pressed
    else:
        # set the steering angle to 0
        car.steering_angle = 0

    # limit the value of range of steering angle between -30 and 30
    car.steering_angle = max(min(car.steering_angle, car.max_steering), -car. max_steering)


    # update the state of car every unit of time
    car.update(dt)

    screen.fill((0,0,0))

    # rotate the car image
    rotated = pygame.transform.rotate(car_image, car.yaw)

    # draw the rotated car image at calculated point
    screen.blit(rotated, [car.car_img_x, car.car_img_y])

    pygame.display.flip()

pygame.quit()
This post is licensed under CC BY 4.0 by the author.