우주선, 소행성 게임 만들기

 

 

 

먼저 폴리곤 모델 정의

폴리곤 모델은 우주선, 행성의미

폴리곤 구성하는 점들의 모임과

x, y, 각도값

x속도, y속도, 각속도

로 구성

 

 

class PolygonModel():
    def __init__(self, points):
        self.points = points
        self.rotation_angle = 0
        self.x = 0
        self.y = 0
        self.vx = 0
        self.vy = 0
        self.angular_velocity = 0

 

폴리곤 모델로 배와 소행성 만들기

폴리곤 모델을 상속받는 우주선과 소행성 클래스 정의

소행성은 5 ~ 9개의 면을 가짐

소행성의 점들은 극좌표로 만든후 카티지안 좌표계로 변환

이때 극좌표 값 r은 0.5 ~ 1.0 균일분포로 뽑고 2 * pi * i /sides 로 theta를 뽑아냄

초기 속도 vx,vy는 0이나 각속도는 균일분포로 뽑아냄

import vectors
from math import pi, sqrt, cos, sin, atan2
from random import randint, uniform

class Ship(PolygonModel):
    def __init__(self):
        super().__init__([(0.5, 0), (-0.25, 0.25), (-0.25,-0.25)])
    
class Asteroid(PolygonModel):
    def __init__(self):
        sides = randint(5, 9)
        vs = [vectors.to_cartesian((uniform(0.5,1.0), 2 * pi * i /sides))
            for i in range(0, sides)]
        super().__init__(vs)
        self.vx = 0
        self.vy = 0
        self.angular_velocity = uniform(-pi/2, pi/2)

 

 

게임 초기화

배와 소행성들을 생성

소행성의 xy는 -9 ~ 9 사이 임의의 정수로 뽑음

ship = Ship()
asteroid_count = 10
asteroids = [Asteroid() for _ in range(0, asteroid_count)]

for ast in asteroids:
    ast.x = randint(-9, 9)
    ast.y = randint(-9, 9)

 

 

 

 

 

 

게임 대충 켜보기

일단 지금 드로잉하는 게임은 width, height 400, 400이고

위에서 작성한 우주선과 소행성의 x, y가 아닌 점들은 0 ~ 1 사이로 정규화된 값을 갖는다.

 

정규화된 x, y값을 실제 게임화면에 맞추기 위한 to_pixel 함수와 비정규화된 xy좌표 값을 가진 pixel을 드로잉하는 함수를 작성

BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
BLUE = (0, 0, 255)
GREEN = (0, 255, 0)
RED = (255, 0, 0)

width, height = 400, 400
def to_pixels(x, y):
    return (width/2 + width * x / 20, height/2 - height * y / 20)

def draw_poly(screen, polygon_model, color=GREEN):
    pixel_points = [to_pixels(x, y) for x, y in polygon_model.transformed()]
    pygame.draw.aalines(screen, color, True, pixel_points, 10)

 

폴리곤 모델은 각도에 따라 포인트 점 위치가 달라지므로 transformed() 함수가 추가됨

init 만 있던 폴리곤 모델에 변환 함수 추가하고

class PolygonModel():
    def __init__(self, points):
        self.points = points
        self.rotation_angle = 0
        self.x = 0
        self.y = 0
        self.vx = 0
        self.vy = 0
        self.angular_velocity = 0
    
    def transformed(self):
        rotated = [vectors.rotate2d(self.rotation_angle, v) for v in self.points]
        return [vectors.add((self.x, self.y), v) for v in rotated]

 

 

 

지금 구현한 사항들을 pygame으로 돌리므로

최소한 화면에 띄을수 있는 코드를 작성해서 돌리면 


def main():
    pygame.init()
    screen = pygame.display.set_mode([width, height])
    clock = pygame.time.Clock()
    done = False

    while not done:
        clock.tick()
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                done = True

        screen.fill(WHITE)

        draw_poly(screen, ship)
        for ast in asteroids:
            draw_poly(screen, ast)
        pygame.display.flip()

    pygame.quit()
if __name__== "__main__":
    main()

우주선 1개랑

소행성 10개가 파이게임 스크린에 나온다.

 

 

지금까지 전체코드

import pygame
import vectors
from math import pi, sqrt, cos, sin, atan2
from random import randint, uniform

class PolygonModel():
    def __init__(self, points):
        self.points = points
        self.rotation_angle = 0
        self.x = 0
        self.y = 0
        self.vx = 0
        self.vy = 0
        self.angular_velocity = 0
    
    def transformed(self):
        rotated = [vectors.rotate2d(self.rotation_angle, v) for v in self.points]
        return [vectors.add((self.x, self.y), v) for v in rotated]


class Ship(PolygonModel):
    def __init__(self):
        super().__init__([(0.5, 0), (-0.25, 0.25), (-0.25,-0.25)])
    
class Asteroid(PolygonModel):
    def __init__(self):
        sides = randint(5, 9)
        vs = [vectors.to_cartesian((uniform(0.5,1.0), 2 * pi * i /sides))
            for i in range(0, sides)]
        super().__init__(vs)
        self.vx = 0
        self.vy = 0
        self.angular_velocity = uniform(-pi/2, pi/2)

ship = Ship()
asteroid_count = 10
asteroids = [Asteroid() for _ in range(0, asteroid_count)]

for ast in asteroids:
    ast.x = randint(-9, 9)
    ast.y = randint(-9, 9)

BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
BLUE = (0, 0, 255)
GREEN = (0, 255, 0)
RED = (255, 0, 0)

width, height = 400, 400
def to_pixels(x, y):
    return (width/2 + width * x / 20, height/2 - height * y / 20)

def draw_poly(screen, polygon_model, color=GREEN):
    pixel_points = [to_pixels(x, y) for x, y in polygon_model.transformed()]
    pygame.draw.aalines(screen, color, True, pixel_points, 10)


def main():
    pygame.init()
    screen = pygame.display.set_mode([width, height])
    clock = pygame.time.Clock()
    done = False

    while not done:
        clock.tick()
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                done = True

        screen.fill(WHITE)

        draw_poly(screen, ship)
        for ast in asteroids:
            draw_poly(screen, ast)
        pygame.display.flip()

    pygame.quit()
if __name__== "__main__":
    main()

 

 

 

 

소행성 이동시키기

화면에 띄운건 좋은데 아무것도 움직이지 못하니 좀 그렇다.

먼저 소행성부터 시간에따라 움직이도록 해보자

 

아직 polygonmodel에 move는 구현안했지만

일단 메인에 이런식으로 밀리세컨드마다 무브하도록 짚어놓고

def main():
    pygame.init()
    screen = pygame.display.set_mode([width, height])
    clock = pygame.time.Clock()
    done = False

    while not done:
        clock.tick()
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                done = True
        
        miliseconds = clock.get_time()
        for ast in asteroids:
            ast.move(miliseconds)

 

 

폴리곤 모델에 move를 구현한다.

해당 폴리곤의 밀리세컨드 시간에 따라 x, y와 회전각을 갱신시킨다.

class PolygonModel():
    def __init__(self, points):
        self.points = points
        self.rotation_angle = 0
        self.x = 0
        self.y = 0
        self.vx = 0
        self.vy = 0
        self.angular_velocity = 0
    
    def transformed(self):
        rotated = [vectors.rotate2d(self.rotation_angle, v) for v in self.points]
        return [vectors.add((self.x, self.y), v) for v in rotated]

    def move(self, milliseconds):
        dx, dy = self.vx * milliseconds / 1000.0, self.vy * milliseconds/ 1000.0
        self.x, self.y = vectors.add((self.x, self.y), (dx, dy))
        self.rotation_angle += self.angular_velocity * milliseconds / 1000.0

 

근대 생각해보니 아까 소행성 vx, vy를 0으로 해놔서

위 코드에선 소행성이 회전만 한다

이동하도록 하기위해 vx, vy를 균일분포 -1, 1로 해놓자

class Asteroid(PolygonModel):
    def __init__(self):
        sides = randint(5, 9)
        vs = [vectors.to_cartesian((uniform(0.5,1.0), 2 * pi * i /sides))
            for i in range(0, sides)]
        super().__init__(vs)
        self.vx = uniform(-1, 1)
        self.vy = uniform(-1, 1)
        self.angular_velocity = uniform(-pi/2, pi/2)

 

우주선이랑 소행성 구분이 안가는것 때문에

배는 draw poly에서 색을 빨강으로 설정

 

아무튼 이제 소행성이 회전도 하고 이동도 한다.

 

import pygame
import vectors
from math import pi, sqrt, cos, sin, atan2
from random import randint, uniform

class PolygonModel():
    def __init__(self, points):
        self.points = points
        self.rotation_angle = 0
        self.x = 0
        self.y = 0
        self.vx = 0
        self.vy = 0
        self.angular_velocity = 0
    
    def transformed(self):
        rotated = [vectors.rotate2d(self.rotation_angle, v) for v in self.points]
        return [vectors.add((self.x, self.y), v) for v in rotated]

    def move(self, milliseconds):
        dx, dy = self.vx * milliseconds / 1000.0, self.vy * milliseconds/ 1000.0
        self.x, self.y = vectors.add((self.x, self.y), (dx, dy))
        self.rotation_angle += self.angular_velocity * milliseconds / 1000.0


class Ship(PolygonModel):
    def __init__(self):
        super().__init__([(0.5, 0), (-0.25, 0.25), (-0.25,-0.25)])
    
class Asteroid(PolygonModel):
    def __init__(self):
        sides = randint(5, 9)
        vs = [vectors.to_cartesian((uniform(0.5,1.0), 2 * pi * i /sides))
            for i in range(0, sides)]
        super().__init__(vs)
        self.vx = uniform(-1, 1)
        self.vy = uniform(-1, 1)
        self.angular_velocity = uniform(-pi/2, pi/2)

ship = Ship()
asteroid_count = 10
asteroids = [Asteroid() for _ in range(0, asteroid_count)]

for ast in asteroids:
    ast.x = randint(-9, 9)
    ast.y = randint(-9, 9)

BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
BLUE = (0, 0, 255)
GREEN = (0, 255, 0)
RED = (255, 0, 0)

width, height = 400, 400
def to_pixels(x, y):
    return (width/2 + width * x / 20, height/2 - height * y / 20)

def draw_poly(screen, polygon_model, color=GREEN):
    pixel_points = [to_pixels(x, y) for x, y in polygon_model.transformed()]
    pygame.draw.aalines(screen, color, True, pixel_points, 10)


def main():
    pygame.init()
    screen = pygame.display.set_mode([width, height])
    clock = pygame.time.Clock()
    done = False

    while not done:
        clock.tick()
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                done = True
        
        miliseconds = clock.get_time()
        for ast in asteroids:
            ast.move(miliseconds)

        screen.fill(WHITE)

        draw_poly(screen, ship, color="red")
        for ast in asteroids:
            draw_poly(screen, ast)
        pygame.display.flip()

    pygame.quit()
if __name__== "__main__":
    main()

 

 

우주선 회전하기

원래 우주선 회전 + 이동 둘다 구현이 되어있는줄 알았는데

깃헙 자료상 회전만 있다 .... 시간만 들이면 전후진 구현할수 있긴한데 너무 복잡하니 이번엔 회전 추가

 

복잡한건 없고, 위 코드의 메인함수에

키입력에 따라 배의 회전각만 변경시켜주는게 전부다

def main():
    pygame.init()
    screen = pygame.display.set_mode([width, height])
    clock = pygame.time.Clock()
    done = False

    while not done:
        clock.tick()
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                done = True
        
        miliseconds = clock.get_time()
        for ast in asteroids:
            ast.move(miliseconds)

        keys = pygame.key.get_pressed()
        if keys[pygame.K_LEFT]:
            ship.rotation_angle += miliseconds * (2 * pi / 1000)
        if keys[pygame.K_RIGHT]:
            ship.rotation_angle -= miliseconds * (2 * pi / 1000)

        screen.fill(WHITE)

        draw_poly(screen, ship, color="red")
        for ast in asteroids:
            draw_poly(screen, ast)
        pygame.display.flip()

    pygame.quit()

 

 

 

 

 

레이저 구현하기

소행성도 움직이고, 우주선도 회전하겠다 이젠 레이저 쏘기 구현하자.

레이저는 직선 형태니까 draw segment 함수로 구현, 2점이 주어질때 빨간색 드로잉하는 내용

 

main함수에는 ship.laser_segment()로 레이저 정보 가져오고,

space 눌렀을때 드로잉 한다.(?)

스페이스 안눌러도 레이저 날라가는게 남아있어야 하지 않나 싶긴한데 일단 그냥 진행

def draw_segment(screen, v1, v2, color=RED):
    pygame.draw.aaline(screen, color, to_pixels(*v1), to_pixels(*v2), 10)


def main():
    pygame.init()
    screen = pygame.display.set_mode([width, height])
    clock = pygame.time.Clock()
    done = False

    while not done:
        clock.tick()
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                done = True
        
        miliseconds = clock.get_time()
        for ast in asteroids:
            ast.move(miliseconds)

        keys = pygame.key.get_pressed()
        if keys[pygame.K_LEFT]:
            ship.rotation_angle += miliseconds * (2 * pi / 1000)
        if keys[pygame.K_RIGHT]:
            ship.rotation_angle -= miliseconds * (2 * pi / 1000)

        laser = ship.laser_segment()

        screen.fill(WHITE)

        if keys[pygame.K_SPACE]:
            draw_segment(screen, *laser)

        draw_poly(screen, ship, color="red")
        for ast in asteroids:
            draw_poly(screen, ast)
        pygame.display.flip()

    pygame.quit()

 

 

ship의 laser segment를 보니 왜 눌른 동안만 드로잉하게 된지 알겠다.

소행성과 우주선은 -10 ~ 10의 좌표 범위를 갖고있지만 to_pixels로 스크린에 맞게 400, 400으로 매칭되어있다.

레이저는 우주선 중앙부터 20 * root(2)로 대충 화면 너머까지 쏘개하도록 되어있음

레이저 날라가는 동작 없이 없이 발사하면 끝까지 나가도록 되어있기 때문

 

레이저 세그먼트는

레이저 길이를 20 * root 2

시작점은 우주선중앙

끝점은 이 레이저 길이 x cos, sin으로 우주선 방향 끝까지 넘어가도록 되어있음

 

class Ship(PolygonModel):
    def __init__(self):
        super().__init__([(0.5, 0), (-0.25, 0.25), (-0.25,-0.25)])
    
    def laser_segment(self):
        dist = 20 * sqrt(2)
        x, y = self.transformed()[0]
        return (x, y), (x + dist * cos(self.rotation_angle), y + dist*sin(self.rotation_angle))

 

 

레이저를 쏘면 화면끝까지 날라간다.

import pygame
import vectors
from math import pi, sqrt, cos, sin, atan2
from random import randint, uniform

class PolygonModel():
    def __init__(self, points):
        self.points = points
        self.rotation_angle = 0
        self.x = 0
        self.y = 0
        self.vx = 0
        self.vy = 0
        self.angular_velocity = 0
    
    def transformed(self):
        rotated = [vectors.rotate2d(self.rotation_angle, v) for v in self.points]
        return [vectors.add((self.x, self.y), v) for v in rotated]

    def move(self, milliseconds):
        dx, dy = self.vx * milliseconds / 1000.0, self.vy * milliseconds/ 1000.0
        self.x, self.y = vectors.add((self.x, self.y), (dx, dy))
        self.rotation_angle += self.angular_velocity * milliseconds / 1000.0


class Ship(PolygonModel):
    def __init__(self):
        super().__init__([(0.5, 0), (-0.25, 0.25), (-0.25,-0.25)])
    
    def laser_segment(self):
        dist = 20 * sqrt(2)
        x, y = self.transformed()[0]
        return (x, y), (x + dist * cos(self.rotation_angle), y + dist*sin(self.rotation_angle))
    
    
class Asteroid(PolygonModel):
    def __init__(self):
        sides = randint(5, 9)
        vs = [vectors.to_cartesian((uniform(0.5,1.0), 2 * pi * i /sides))
            for i in range(0, sides)]
        super().__init__(vs)
        self.vx = uniform(-1, 1)
        self.vy = uniform(-1, 1)
        self.angular_velocity = uniform(-pi/2, pi/2)

ship = Ship()
asteroid_count = 10
asteroids = [Asteroid() for _ in range(0, asteroid_count)]

for ast in asteroids:
    ast.x = randint(-9, 9)
    ast.y = randint(-9, 9)

BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
BLUE = (0, 0, 255)
GREEN = (0, 255, 0)
RED = (255, 0, 0)

width, height = 400, 400

def to_pixels(x, y):
    return (width/2 + width * x / 20, height/2 - height * y / 20)

def draw_poly(screen, polygon_model, color=GREEN):
    pixel_points = [to_pixels(x, y) for x, y in polygon_model.transformed()]
    pygame.draw.aalines(screen, color, True, pixel_points, 10)

def draw_segment(screen, v1, v2, color=RED):
    pygame.draw.aaline(screen, color, to_pixels(*v1), to_pixels(*v2), 10)


def main():
    pygame.init()
    screen = pygame.display.set_mode([width, height])
    clock = pygame.time.Clock()
    done = False

    while not done:
        clock.tick()
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                done = True
        
        miliseconds = clock.get_time()
        for ast in asteroids:
            ast.move(miliseconds)

        keys = pygame.key.get_pressed()
        if keys[pygame.K_LEFT]:
            ship.rotation_angle += miliseconds * (2 * pi / 1000)
        if keys[pygame.K_RIGHT]:
            ship.rotation_angle -= miliseconds * (2 * pi / 1000)

        laser = ship.laser_segment()

        screen.fill(WHITE)

        if keys[pygame.K_SPACE]:
            draw_segment(screen, *laser)

        draw_poly(screen, ship, color="blue")
        for ast in asteroids:
            draw_poly(screen, ast)
        pygame.display.flip()

    pygame.quit()
if __name__== "__main__":
    main()

 

 

 

레이저와 소행성 충돌 판정하기

소행성 게임도 이제 거의 다되가지만 가장 중요한

레이저와 소행성 충돌 판정이 남아있다.

일단 메인 함수에 레이저랑 충돌하면 제거하고, 충돌안하면 그대로 드로잉 하도록 고치자

 

여기선 space가 눌린 상태에 ast.does_intersect(laser)가 true 시 소행성 제거하게된다.

def main():
    pygame.init()
    screen = pygame.display.set_mode([width, height])
    clock = pygame.time.Clock()
    done = False

    while not done:
        clock.tick()
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                done = True
        
        miliseconds = clock.get_time()
        for ast in asteroids:
            ast.move(miliseconds)

        keys = pygame.key.get_pressed()
        if keys[pygame.K_LEFT]:
            ship.rotation_angle += miliseconds * (2 * pi / 1000)
        if keys[pygame.K_RIGHT]:
            ship.rotation_angle -= miliseconds * (2 * pi / 1000)

        laser = ship.laser_segment()

        screen.fill(WHITE)

        if keys[pygame.K_SPACE]:
            draw_segment(screen, *laser)

        draw_poly(screen, ship, color="blue")
        for ast in asteroids:
            if keys[pygame.K_SPACE] and ast.does_intersect(laser):
                asteroids.remove(ast)
            else:
                draw_poly(screen, ast)

        pygame.display.flip()

 

does_intersect는 소행성이 아니라 폴리곤 모델에 구현

직선간 겹침을 판단하기위해 폴리곤을 이루는 모든 직선은 세그먼트로 가져와 사용한다.

 

세그먼츠는 원래 폴리곤 모델의 포인트를 회전 변환후, 현재 인덱스와 다음 인덱스 두 점의 튜플 리스트

인자인 other_segment는 레이저이므로

레이저와 세그먼츠를 이루는 직선 세금먼트간에 교점있는지 여부를 do segments intersect에서 다룬다.

class PolygonModel():
    def __init__(self, points):
        self.points = points
        self.rotation_angle = 0
        self.x = 0
        self.y = 0
        self.vx = 0
        self.vy = 0
        self.angular_velocity = 0
    
    def transformed(self):
        rotated = [vectors.rotate2d(self.rotation_angle, v) for v in self.points]
        return [vectors.add((self.x, self.y), v) for v in rotated]

    def move(self, milliseconds):
        dx, dy = self.vx * milliseconds / 1000.0, self.vy * milliseconds/ 1000.0
        self.x, self.y = vectors.add((self.x, self.y), (dx, dy))
        self.rotation_angle += self.angular_velocity * milliseconds / 1000.0
    
    def segments(self):
        point_count = len(self.points)
        points = self.transformed()
        return [(points[i], points[(i+1)%point_count]) for i in range(0, point_count)]
    
    def does_intersect(self, other_segment):
        for segment in self.segments():
            if do_segments_intersect(other_segment, segment):
                return True
        return False

 

 

 

 

연립일차방정식으로 레이저와 소행성 충돌 풀기

위에서 do_segments_intersect로 두 세그먼트가 주어질떄 교차되는지 판정하는 내용을 구현해야한다.

각 세그먼트는 두 점으로 이뤄져있으므로 각각 직선의 방정식을 구해서 해를 찾으면 된다.

두 직선의 교점, 연립일차방정식의 해를 구한다.

 

대충 소행성의 선분 세그먼트가 (2, 4)와 (4, 2)를 지나면 직선의 방정식으로 y = -x + 6

레이저 선분 세그먼트 직선의 방정식은 대충 y = x라 하자

 

그러면 일차식 표준형 ax + by = c로 바꾸면

좌측 하단과 같다.

이 두 일차식 표준형은 행렬로 다루면 

여기서 역행렬을 곱해주고 계산하면 두 직선의 교점 xy를 얻을수 있음

이경우엔 3, 3이 된다.

 

 

 

 

하지만 이런 경우 처럼 두 직선의 교점이 소행성 선분 밖에 존재할 수도 있음

 

이런 경우를 처리하기 위해

레이저 길이 d1, 소행성 선분 길이 d2로 놓고

 

다음 4가지 경우가 성립할떄, 두 직선이 제대로 만난다고 판단한다.

 

1. 소행성 선분 한점 u1, 교점 사이 길이가 d2 보다 작음

2. 소행성 선분 다른점 u2, 교점 사이 길이가 d2보다 작음

3. 레이저 한점 v1, 교점사이 길이가 d1보다 작음

4. 레이저 한점 v2, 교점사이 길이가 d1 보다 작음

 

아래 그림의 경우 교점이 (3,3)에 있어 레이저 길이 d1보다 작으므로 3, 4번 조건은 만족하지만

왼쪽 점을 u1이라 할경우, u1에서 (3, 3)거리는 d2보다 크므로 1번은 만족하지 않는다.

오른쪽 점을 u2라 하는 경우, u2에서 (3, 3)까지 거리는 d2보다 작아 만족한다.

 

이 4가지 경우를 곱하면 1번 조건을 불만족하므로 교차하지 않는 것으로 판단한다.

 

 

 

does_intersect 안에있는 do_segment_intersect를 구현하면 다음과 같다.

do_segment_intersect에선 세그먼트 s1, 세그먼트 s2를 받아

선분의 길이 d1, d2를 구하고

insersection에서 두 세그먼트의 교점 xy를 구한다.

아까 위에서 말한 4가지 경우에 따라 선분 길이 d1,d2와 xy교점과 세그먼트 점 길이로 충돌 여부를 판단한다.

 

intersection에서는 각 세그먼트를 구성하는 두점을 일차식 표준형으로 만들고

넘파이로 해를 구해서 반환한다.

 

나머지는 이 과정에 사용되는 전체 함수

 

def subtract(v1, v2):
    return tuple(v1-v2 for (v1,v2) in zip(v1, v2))

def length(v):
    return sqrt(sum([coord ** 2 for coord in v]))

def distance(v1, v2):
    return length(subtract(v1, v2))

def standard_form(v1, v2):
    x1, y1 = v1
    x2, y2 = v2
    a = y2 - y1
    b = x1 - x2
    c = x1 * y2 - y1 * x2
    return a, b, c

def intersection(u1, u2, v1, v2):
    a1, b1, c1 = standard_form(u1, u2)
    a2, b2, c2 = standard_form(v1, v2)
    m = np.array(((a1, b1), (a2, b2)))
    c = np.array((c1, c2))
    return np.linalg.solve(m, c)

def do_segments_intersect(s1, s2):
    u1, u2 = s1
    v1, v2 = s2
    d1, d2 = distance(*s1), distance(*s2)
    try:
        x, y = intersection(u1, u2, v1, v2)
        return (distance(u1, (x,y)) <= d1 and
                distance(u2, (x, y)) <= d1 and
                distance(v1, (x, y)) <= d2 and
                distance(v2, (x, y)) <= d2)
    except np.linalg.linalg.LinAlgError:
        return False

 

 

 

 

 

 

 

완성

이제 우주선에서 레이저를 쏘면 동작(평행이동, 회전)하는 소행성들의 충돌 여부를 판단해서

제거하는 간단한 게임 완성

 

 

 

 

 

import pygame
import vectors
import numpy as np
from math import pi, sqrt, cos, sin, atan2
from random import randint, uniform

def subtract(v1, v2):
    return tuple(v1-v2 for (v1,v2) in zip(v1, v2))

def length(v):
    return sqrt(sum([coord ** 2 for coord in v]))

def distance(v1, v2):
    return length(subtract(v1, v2))

def standard_form(v1, v2):
    x1, y1 = v1
    x2, y2 = v2
    a = y2 - y1
    b = x1 - x2
    c = x1 * y2 - y1 * x2
    return a, b, c

def intersection(u1, u2, v1, v2):
    a1, b1, c1 = standard_form(u1, u2)
    a2, b2, c2 = standard_form(v1, v2)
    m = np.array(((a1, b1), (a2, b2)))
    c = np.array((c1, c2))
    return np.linalg.solve(m, c)

def do_segments_intersect(s1, s2):
    u1, u2 = s1
    v1, v2 = s2
    d1, d2 = distance(*s1), distance(*s2)
    try:
        x, y = intersection(u1, u2, v1, v2)
        return (distance(u1, (x,y)) <= d1 and
                distance(u2, (x, y)) <= d1 and
                distance(v1, (x, y)) <= d2 and
                distance(v2, (x, y)) <= d2)
    except np.linalg.linalg.LinAlgError:
        return False


class PolygonModel():
    def __init__(self, points):
        self.points = points
        self.rotation_angle = 0
        self.x = 0
        self.y = 0
        self.vx = 0
        self.vy = 0
        self.angular_velocity = 0
    
    def transformed(self):
        rotated = [vectors.rotate2d(self.rotation_angle, v) for v in self.points]
        return [vectors.add((self.x, self.y), v) for v in rotated]

    def move(self, milliseconds):
        dx, dy = self.vx * milliseconds / 1000.0, self.vy * milliseconds/ 1000.0
        self.x, self.y = vectors.add((self.x, self.y), (dx, dy))
        self.rotation_angle += self.angular_velocity * milliseconds / 1000.0
    
    def segments(self):
        point_count = len(self.points)
        points = self.transformed()
        return [(points[i], points[(i+1)%point_count]) for i in range(0, point_count)]
    
    def does_intersect(self, other_segment):
        for segment in self.segments():
            if do_segments_intersect(other_segment, segment):
                return True
        return False


class Ship(PolygonModel):
    def __init__(self):
        super().__init__([(0.5, 0), (-0.25, 0.25), (-0.25,-0.25)])
    
    def laser_segment(self):
        dist = 20 * sqrt(2)
        x, y = self.transformed()[0]
        return (x, y), (x + dist * cos(self.rotation_angle), y + dist*sin(self.rotation_angle))
    
    
class Asteroid(PolygonModel):
    def __init__(self):
        sides = randint(5, 9)
        vs = [vectors.to_cartesian((uniform(0.5,1.0), 2 * pi * i /sides))
            for i in range(0, sides)]
        super().__init__(vs)
        self.vx = uniform(-1, 1)
        self.vy = uniform(-1, 1)
        self.angular_velocity = uniform(-pi/2, pi/2)

ship = Ship()
asteroid_count = 10
asteroids = [Asteroid() for _ in range(0, asteroid_count)]

for ast in asteroids:
    ast.x = randint(-9, 9)
    ast.y = randint(-9, 9)

BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
BLUE = (0, 0, 255)
GREEN = (0, 255, 0)
RED = (255, 0, 0)

width, height = 400, 400

def to_pixels(x, y):
    return (width/2 + width * x / 20, height/2 - height * y / 20)

def draw_poly(screen, polygon_model, color=GREEN):
    pixel_points = [to_pixels(x, y) for x, y in polygon_model.transformed()]
    pygame.draw.aalines(screen, color, True, pixel_points, 10)

def draw_segment(screen, v1, v2, color=RED):
    pygame.draw.aaline(screen, color, to_pixels(*v1), to_pixels(*v2), 10)


def main():
    pygame.init()
    screen = pygame.display.set_mode([width, height])
    clock = pygame.time.Clock()
    done = False

    while not done:
        clock.tick()
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                done = True
        
        miliseconds = clock.get_time()
        for ast in asteroids:
            ast.move(miliseconds)

        keys = pygame.key.get_pressed()
        if keys[pygame.K_LEFT]:
            ship.rotation_angle += miliseconds * (2 * pi / 1000)
        if keys[pygame.K_RIGHT]:
            ship.rotation_angle -= miliseconds * (2 * pi / 1000)

        laser = ship.laser_segment()

        screen.fill(WHITE)

        if keys[pygame.K_SPACE]:
            draw_segment(screen, *laser)

        draw_poly(screen, ship, color="blue")
        for ast in asteroids:
            if keys[pygame.K_SPACE] and ast.does_intersect(laser):
                asteroids.remove(ast)
            else:
                draw_poly(screen, ast)

        pygame.display.flip()

    pygame.quit()
if __name__== "__main__":
    main()

 

 

+ Recent posts