개발 환경 구축
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
- numpy사이트로 가서 파이썬 3.7로 다운
visual studio 설치
자신에 맞는 버전을 다운 받으면 된다.
커뮤니티버전으로 설치하고, c++를 사용한 데스크톱 개발
, MSVC v141 - VS 2017 C++ ~
클릭
Pygame 예제
간단한 집 그리기
https://kkamikoon.tistory.com/129
- pygame 선언 (import)
- pygame 초기화 (pygame.init())
- pygame에서 사용할 전역 변수 선언
- size : x,y 크기
- screen : pygame.display.set_mode(size)
- clock : pygame.time.Clock()
- 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
스크린에 차량을 먼저 표기하고 키보드로 움직이도록 하고자 한다.
처음 셋팅
- 저장된 차량 이미지를 표시
- 처음 위치 셋팅
- 차량 바퀴는 x
- 스크린 크기 1280x720
- 바탕은 검정
행동 감지
- 위방향 -> 전진할 때는 가속, 후진할 때는 브레이크
- 아래방향 -> 전진할 때는 브레이크, 후진할 때는 가속
- 오른방향 -> 바퀴를 오른쪽으로 꺽음, 최대 꺽임 각도 30도
- 왼방향 -> 바퀴를 왼쪽으로 꺽음, 최대 꺽임 각도는 -30도
- 스페이스바는 방향 상관없이 브레이크
좌우 핸들링
- 좌우 키를 누르고 있으면 바퀴가 조금씩 좌/우로 돌아감
- 키에서 손을 떼면 바퀴가 즉시 정면으로 되돌아와 정렬된다.
전진
- 위쪽 키를 누르고 있으면 엑셀을 계속 밟는 것처럼 속도가 점점 빨라진다.
- 키에서 손을 떼면 엑셀에서 발을 뗀 것처럼 속도가 점차 줄어서 멈춘다
- 아래 방향을 누르면 브레이크가 걸린 것처럼 속도가 빨리 줄어서 멈춘다.(멈추고도 계속 누르면 후진 가속, 정차시키려면 스페이스바를 누른다)
후진
- 전과 동
- 키만 반대
HLD(High Level Design) 설계
- Car 클래스 정의 (위치/자세 정보)
- pygame 초기화
- 윈도우 타이틀 지정 / 크기 설정
- 전역 변수 설정 / 클래스 객체 생성
- while not exit_flags:
- for 루프(pygame.QUIT 이벤트 처리)
- 키 입력값 읽어들여서 if/elif/else 블록에서 처리 7-1. if pressed[pygame.K_UP] : if # 선속도가 음수면 , else # 선속도가 양수면
- 차량의 새로운 위치/자세 계산하고 그림 새로 그리기
- 화면 업데이트
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()