드디어 장을 다뤄보게 됬다

 

벡터장 플로팅 하기

좌표 x,y를 입력받아 반환하는 다음 함수를 주면 벡터를 반환

f(x, y) = (-2y, x)

 

import numpy as np
import matplotlib.pyplot as plt

def f(x, y):
    return (-2*y, x)

def plot_vector_field(f, xmin, xmax, ymin, ymax, xstep=1, ystep=1):
    X, Y = np.meshgrid(np.arange(xmin, xmax, xstep), np.arange(ymin, ymax, ystep))
    U = np.vectorize(lambda x, y : f(x, y)[0])(X, Y)
    V = np.vectorize(lambda x, y : f(x, y)[1])(X, Y)
    plt.quiver(X, Y, U, V, color='red')
    fig = plt.gcf()
    fig.set_size_inches(7,7)


plot_vector_field(f, -5, 5, -5, 5)

 

 

x, y = 1, 2 인경우

f(1, 2) = (-5, 1)인 벡터를 반환

meshgrid xy 둘다 -5 ~ 5 범위로 step은 1로 해서

이런 식으로 반환

 

 

-5, -5가 f에 들어갔다고 하자

f(-5, -5) = (10, -5)가 된다.

U, V 행렬 첫 값을 보면 생각한대로 나온다.

그리고 plt.quiver()는 2d 화살표 장을 플로팅 해주는 함수라고 한다.

 

 

위 사진에서 잘렸는데

x,y = -5, -5에서 벡터는 (10, -5)인데 사진 밖에 나가서 보이질 않는다

대신 x, y = -3, -3이라고 하면 벡터는 (6, -3)

-3, -3위치의 화살표를 보면 대충 느낌은 맞는거같다

 

 

이번엔 함수를 f(x,y) = (-x, -y)로 바꿔보면 이런식으로 중앙을 향하는 벡터장이 나온다.

 

 

 

벡터장의 중심을 -2, 4로 이동시키기

함수 -2-x, 4-y로 하면 벡터장의 중심이 -2, 4로 이동한 결과가 나온다.

 

 

스칼라장 플로팅하기

이번에는 스칼라장을 띄워봄

포텐셜 함수 u(x, y) = 0.5 * (x^2 + y^2)를 플로팅하면

 

 

from matplotlib.pyplot import cm
def plot_scalar_field(
            f, xmin, xmax, ymin, ymax, xstep=0.25, ystep=0.25, 
            c=None, cmap=cm.coolwarm, alpha=1, antialiased=False
        ):
    fig = plt.figure()
    fig.set_size_inches(7, 7)
    ax = fig.gca(projection='3d')
    fv = np.vectorize(f)
    X = np.arange(xmin, xmax, xstep)
    Y = np.arange(ymin, ymax, ystep)
    X, Y = np.meshgrid(X, Y)
    Z = fv(X, Y)
    surf = ax.plot_surface(X, Y, Z, cmap=cmap, color=c, alpha=alpha,
                             linewidth=0, antialiased=antialiased)
def u(x, y):
    return 0.5 * (x**2 + y**2)
plot_scalar_field(u, -5, 5, -5, 5)

 

 

히트맵으로 플로팅하면 이런식

def scalar_field_heatmap(f, xmin, xmax, ymin, ymax, xstep=0.1, ystep=0.1):
    fig = plt.figure()
    fig.set_size_inches(7,7)
    fv = np.vectorize(f)
    X = np.arange(xmin, xmax, xstep)
    Y = np.arange(ymin, ymax, ystep)
    X, Y = np.meshgrid(X, Y)
    z = fv(X, Y)
    fig, ax = plt.subplots()
    c = ax.pcolormesh(X, Y, z, cmap='plasma')
    ax.axis([X.min(), X.max(), Y.min(), Y.max()])
    fig.colorbar(c, ax=ax)
    plt.xlabel('x')
    plt.ylabel('y')
scalar_field_heatmap(u, -5, 5, -5, 5)

 

이번엔 스칼라장을 등고선으로 플로팅

def scalar_field_contour(f, xmin, xmax, ymin, ymax, levels=None):
    fv = np.vectorize(f)
    X = np.arange(xmin, xmax, 0.1)
    Y = np.arange(ymin, ymax, 0.1)
    X, Y = np.meshgrid(X, Y)
    Z = fv(X, Y)
    fig, ax = plt.subplots()
    CS = ax.contour(X, Y, Z, levels=levels)
    ax.clabel(CS, inline=1, fontsize=10, fmt='%1.1f')
    plt.xlabel('x')
    plt.ylabel('y')
    fig.set_size_inches(7, 7)

scalar_field_contour(u, -10, 10, -10, 10, levels=[10, 20, 30, 40, 50, 60])

 

 

지금까지 벡터장과 스칼라장을 플로팅 시켰고

이번에는 우주선 게임에 중력장을 추가

 

 

우주선 게임에 중력장 추가하기

진행할때마다 내용이 달라져서 처음부터 구현함

 

 

 

1. 폴리곤 모델 구현하기

이번 구현 게임은 화면 벽에 닿으면 반대로 튀어나갈수 있게 bounce 여부를 추가

맨앞에 bounce 있으며 bounce 켜짐 여부에 따라 반대편에 튀어나오게 할지, 벽에 부딪히면 튕겨지도록 move에 구현됨

 

그외 차이는 변수에 gravity와 mass가 추가되고

move 함수 내에 가속도를 나타내는 추진력 벡터와 중력장 파트가 추가됨

최종 가속도 = 추력 가속도 + 중력 가속도

 

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

boundce = True

class PolygonModel():
    def __init__(self, points):
        self.points = points
        self.rotation_angle = 0
        self.gravity = 0
        self.mass = 1
        self.x = 0
        self.y = 0
        self.vx = 0
        self.vy = 0
        self.angular_velocity = 0
        self.draw_center = False
    
    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, thrust_vector=(0, 0), gravity_sources=[]):
        ax, ay = thrust_vector
        gx, gy = gravitational_field(gravity_source, self.x, self.y)
        ax += gx
        ay += gy
        self.vx += ax * milliseconds / 1000
        self.vy += ay * milliseconds / 1000
        
        dx, dy = self.vx * milliseconds / 1000, self.vy * milliseconds / 1000
        self.x, self.y = vectors.add((self.x, self.y), (dx, dy))
        
        if boundce:
            if self.x < -10 or self.x > 10:
                self.vx = - self.vx
            if self.y < -10 or self.y > 10:
                self.vy = - self.vy
        else:
            if self.x < -10:
                self.x += 20
            if self.y < -10:
                self.y += 20
            if self.x > 10:
                self.x -= 20
            if self.y > 10:
                self.y -= 20
        self.rotation_angle += self.angular_velocity * milliseconds / 1000
    
    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_collide(self, other_poly):
        for other_segment in other_poly.segments():
            if self.does_intersect(other_segment):
                return True
        return False

    def does_intersect(self, other_segment):
        for segment in self.segments():
            if do_segments_intersect(other_segment, segment):
                return True
        return False

 

2. 중력장 구현하기

폴리곤 모델로 블랙홀 클래스 구현

블랙홀은 폴리곤 모델 move 함수의 gravity sources로 들어감

블랙홀 1개만이 아닌 여러개 영향을 줄수 있음

 

gravitational_field에서는 (x, y) 벡터 반환하나

블랙홀들의 x축 중력, y축 중력들을 합하여 반환해줌

 

class BlackHole(PolygonModel):
    def __init__(self, gravity):
        vs = [vectors.to_cartesian((0.5, 2 * pi * i / 20)) for i in range(0, 20)]
        super().__init__(vs)
        self.gravity = gravity
    

def gravitational_field(sources, x, y):
    fields = [vectors.scale(-source.gravity, (x-source.x, y-source.y))
              for source in sources]
    return vectors.add(*fields)

black_hole = BlackHole(0.1)
black_hole.x, black_hole.y = 0, 0
black_holes = [
    black_hole
]

 

 

 

 

3. 메인함수 제외한 나머지 구현

 

먼저 우주선, 소행성 구현

변경사항은 우주선 시작점 빼곤 없음

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()
ship.x = 7
ship.y = 3
asteroid_count = 10
default_asteroids = [Asteroid() for _ in range(0, asteroid_count)]
for ast in default_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)
LIGHT_GRAY =  (240, 240, 240)
DARK_GRAY = (128, 128, 128)
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=BLACK, fill=False):
    pixel_points = [to_pixels(x, y) for x, y in polygon_model.transformed()]
    if fill:
        pygame.draw.polygon(screen, color, pixel_points, 0)
    else:
        pygame.draw.lines(screen, color, True, pixel_points, 2)


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

def draw_grid(screen):
    for x in range(-9, 10):
        draw_segment(screen, (x, -10), (x, 10), color=LIGHT_GRAY)
    for y in range(-9, 10):
        draw_segment(screen, (-10, y), (10, y), color=LIGHT_GRAY)
    draw_segment(screen, (-10, 0), (10, 0), color=DARK_GRAY)
    draw_segment(screen, (0, -10), (0, 10), color=DARK_GRAY)

thrust = 3

 

 

 

 

메인함수도 크게 바뀐건 없지만

우주선, 소행성 무브에 추력 벡터와 중력 소스가 추가되었으며

추력 벡터를 계산하는 과정

블랙홀을 드로잉하는 과정등이 추가되었음

 

전후진 입력없으면 추력 벡터는 (0, 0)이지만 

입력있을시 우주선 방향에 크기 3인 벡터를 x, y축 방향으로 분해해서 추력 벡터로 무브에 전달

 

 

바운스도 잘되고 블랙홀을 중심으로 움직인다.

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

    while not done:
        clock.tick()
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                done = True
        milliseconds = clock.get_time()
        keys = pygame.key.get_pressed()

        if keys[pygame.K_LEFT]:
            ship.rotation_angle += milliseconds * (2 * pi / 1000)
        if keys[pygame.K_RIGHT]:
            ship.rotation_angle -= milliseconds * (2 * pi / 1000)
        for ast in asteroids:
            ast.move(milliseconds, (0, 0), gravity_sources=black_holes)
        
        ship_thrust_vector = (0, 0)
        if keys[pygame.K_UP]:
            ship_thrust_vector = vectors.to_cartesian((thrust, ship.rotation_angle))
        elif keys[pygame.K_DOWN]:
            ship_thrust_vector = vectors.to_cartesian((-thrust, ship.rotation_angle))
        ship.move(milliseconds, ship_thrust_vector, black_holes)
        laser = ship.laser_segment()


        screen.fill(WHITE)
        draw_grid(screen)
        if keys[pygame.K_SPACE]:
            draw_segment(screen, *laser)
        draw_poly(screen, ship)
        for bh in black_holes:
            draw_poly(screen, bh, fill=True)
        for ast in asteroids:
            if keys[pygame.K_SPACE] and ast.does_intersect(laser):
                asteroids.remove(ast)
            else:
                draw_poly(screen, ast, color=GREEN)
        pygame.display.flip()
    pygame.quit()

if __name__ == "__main__":
    main()

 

 

 

 

이번엔 블랙홀을 2개로 늘린경우

소행성들이 두 블랙홀의 영향을 받아 움직인다.

 

black_hole = BlackHole(0.1)
black_hole.x, black_hole.y = 0, 0


black_hole2 = BlackHole(0.2)
black_hole.x, black_hole.y = -4, -4

black_holes = [
    black_hole,
    black_hole2
]

 

 

 

 

 

 

 

 

 

 

 

전체 코드

 

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

boundce = True

class PolygonModel():
    def __init__(self, points):
        self.points = points
        self.rotation_angle = 0
        self.gravity = 0
        self.mass = 1
        self.x = 0
        self.y = 0
        self.vx = 0
        self.vy = 0
        self.angular_velocity = 0
        self.draw_center = False
    
    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, thrust_vector=(0, 0), gravity_sources=[]):
        ax, ay = thrust_vector
        gx, gy = gravitational_field(gravity_sources, self.x, self.y)
        ax += gx
        ay += gy
        self.vx += ax * milliseconds / 1000
        self.vy += ay * milliseconds / 1000
        
        dx, dy = self.vx * milliseconds / 1000, self.vy * milliseconds / 1000
        self.x, self.y = vectors.add((self.x, self.y), (dx, dy))
        
        if boundce:
            if self.x < -10 or self.x > 10:
                self.vx = - self.vx
            if self.y < -10 or self.y > 10:
                self.vy = - self.vy
        else:
            if self.x < -10:
                self.x += 20
            if self.y < -10:
                self.y += 20
            if self.x > 10:
                self.x -= 20
            if self.y > 10:
                self.y -= 20
        self.rotation_angle += self.angular_velocity * milliseconds / 1000
    
    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_collide(self, other_poly):
        for other_segment in other_poly.segments():
            if self.does_intersect(other_segment):
                return True
        return False

    def does_intersect(self, other_segment):
        for segment in self.segments():
            if do_segments_intersect(other_segment, segment):
                return True
        return False





class BlackHole(PolygonModel):
    def __init__(self, gravity):
        vs = [vectors.to_cartesian((0.5, 2 * pi * i / 20)) for i in range(0, 20)]
        super().__init__(vs)
        self.gravity = gravity
    

def gravitational_field(sources, x, y):
    fields = [vectors.scale(-source.gravity, (x-source.x, y-source.y))
              for source in sources]
    return vectors.add(*fields)

black_hole = BlackHole(0.1)
black_hole.x, black_hole.y = 0, 0


black_hole2 = BlackHole(0.2)
black_hole.x, black_hole.y = -4, -4

black_holes = [
    black_hole,
    black_hole2
]




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()
ship.x = 7
ship.y = 3
asteroid_count = 10
default_asteroids = [Asteroid() for _ in range(0, asteroid_count)]
for ast in default_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)
LIGHT_GRAY =  (240, 240, 240)
DARK_GRAY = (128, 128, 128)
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=BLACK, fill=False):
    pixel_points = [to_pixels(x, y) for x, y in polygon_model.transformed()]
    if fill:
        pygame.draw.polygon(screen, color, pixel_points, 0)
    else:
        pygame.draw.lines(screen, color, True, pixel_points, 2)

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

def draw_grid(screen):
    for x in range(-9, 10):
        draw_segment(screen, (x, -10), (x, 10), color=LIGHT_GRAY)
    for y in range(-9, 10):
        draw_segment(screen, (-10, y), (10, y), color=LIGHT_GRAY)
    draw_segment(screen, (-10, 0), (10, 0), color=DARK_GRAY)
    draw_segment(screen, (0, -10), (0, 10), color=DARK_GRAY)

thrust = 3


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

    while not done:
        clock.tick()
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                done = True
        milliseconds = clock.get_time()
        keys = pygame.key.get_pressed()

        if keys[pygame.K_LEFT]:
            ship.rotation_angle += milliseconds * (2 * pi / 1000)
        if keys[pygame.K_RIGHT]:
            ship.rotation_angle -= milliseconds * (2 * pi / 1000)
        for ast in asteroids:
            ast.move(milliseconds, (0, 0), gravity_sources=black_holes)
        
        ship_thrust_vector = (0, 0)
        if keys[pygame.K_UP]:
            ship_thrust_vector = vectors.to_cartesian((thrust, ship.rotation_angle))
        elif keys[pygame.K_DOWN]:
            ship_thrust_vector = vectors.to_cartesian((-thrust, ship.rotation_angle))
        ship.move(milliseconds, ship_thrust_vector, black_holes)
        laser = ship.laser_segment()


        screen.fill(WHITE)
        draw_grid(screen)
        if keys[pygame.K_SPACE]:
            draw_segment(screen, *laser)
        draw_poly(screen, ship)
        for bh in black_holes:
            draw_poly(screen, bh, fill=True)
        for ast in asteroids:
            if keys[pygame.K_SPACE] and ast.does_intersect(laser):
                asteroids.remove(ast)
            else:
                draw_poly(screen, ast, color=GREEN)
        pygame.display.flip()
    pygame.quit()

if __name__ == "__main__":
    main()

 

 

 

+ Recent posts