[게임 프로그래밍] Steering Behaviors
cocos
의 particle system
및 Steering Behaviors
에 대해 공부해볼 것이다.
💡 Cocos Particle System
➖ Base Class
class ParticleSystem(fallback=None, texture=None)
➖ 파생 Class
- class
Fireworks(fallback=None, texture=None)
- class
Spiral(fallback=None, texture=None)
- class
Meteor(fallback=None, texture=None)
- class
Sun(fallback=None, texture=None)
- class
Galaxy(fallback=None, texture=None)
- class
Flower(fallback=None, texture=None)
- class
Explosion(fallback=None, texture=None)
- class
Smoke(fallback=None, texture=None)
Cocos Particle class 예제
import cocos
import cocos.particle_systems as ps
class MainLayer(cocos.layer.Layer):
def __init__(self):
super(MainLayer, self).__init__()
particles = ps.Fireworks(fallback = False)
'''
fallback: Defaults to None.
- False: use point sprites, faster, not always available
- True: use quads, slower but always available
- None: auto-detect, use the fastest available
'''
#particles.angle = 180
#particles.size = 3
particles.position = (320, 240)
self.add(particles) #레이어에 추가
if __name__ == '__main__':
cocos.director.director.init(caption='Particle example')
scene = cocos.scene.Scene(MainLayer()) #scene에 Layer추가
cocos.director.director.run(scene) # scene run
Cocos Particle class 메소드
active
,duration
,gravity
,angle
등..- 가속도의 tangential한지[접선], Radial한지
Particle system 도식화
기존의 CocosNode
가 가지는 Sprite
, Layer
처럼 ParticleSystem
노드를 가지고 CocosNode
의 메소드를 사용할 수 있다. (do(), pause(), stop(), Kill(), schedule()
)
💡 Steering Behaviors
총 5단계로 분류할 수 있다.
📌 Seek and Flee
STATIC한 타겟에 대해 쫓아가고 도망가는 것
📌 Arrival
어느 위치에 찾아가는 것, 속도 등에 대한 동작 에 변화를 줄 수 있음.
📌 Pursuit and evade
MOVING하는 타겟에 대해 추격하는 것
📌 Wander
배회 서성거림
📌 Obstacle avoidance
장애물을 회피하는 것
Seek and Flee
상대를 추격하고 쫓아가는 과정에서 두 가지 방법이 있다.
- 바로 방향 바꾸는 경우 :진행방향 (벡터)만 바꾸어주면 됌
- 서서히 방향을 바꾸는 경우(기존의 속도를 가지는 경우): 벡터에 대해 조금씩 수정이 필요함
NPC의 멤버
- Velocity
- Position
벡터의 합으로 이동 방향을 점진적으로 바꿔주는 것
Seek and Flee: 추격&도망
Limit Vector
움직일 벡터를 계산하는데 벡터의 리미트값을 정해주어서 최대갈 수 있는 벡터값을 설정한다.
magnitude
는 벡터의 크기인데, 파라미터 m보다 크면 벡터를 벡터의 크기로 나누고 m만큼 곱해주어 m크기의 벡터로 만들어준다.
def truncate(vector, m): # limites vector
magnitude = abs(vector) #크기 -> 루트 x^2+y^2
if magnitude > m: #크기가 m보다 크면 limit를 m으로 바꾸는
vector *= m / magnitude
return vector
Update : Seek, Flee
Seek의 과정은 Update
함수에서 Target 벡터값을 계속 받아와서 구현하는데 내용은 아래와 같다.
1. target벡터에서 self
Actor벡터를 빼주어 distance
벡터를 구한다.
2. distance
벡터에서 self
가 가지는 기존의 velocity
벡터를 빼주어 우리가 원하는 방향의 벡터인 Steering
벡터를 구해준다.
3. steering
벡터를 앞에서 제한값을 두는 truncate
함수로 제한 값을 두고 새로운 velocity
로 설정한다.
4. position
값을 누적해서 더하여 이동한다.
그림을 실행해보면 아래와 같다. 처음 velocity
가 위 방향이었다가 target이 오른쪽 아래에 있으니 steering
으로 새 velocity
를 얻어 이동하고 또 새 target이 생기니 새로운 velocity를 얻어 진행방향을 바꾸어 쫓아온다.
flee의 경우에는 이동 방향을 반대로 하기 위해서 self.seek
bool 타입을 두어 false인 경우 -1
을 곱해주어 반대 방향으로 이동하도록 한다.
flee의 여러 가지 구현 방법
처음distance
를 구할 때 flee 인 경우 반대 방향으로distance
벡터로 설정하거나 또는 ,steering
벡터를 반대로 설정할 수도 있다.def update(self, dt): #update에서 target의 벡터값 계속 업데이트받음. if self.target is None: return distance = self.target - eu.Vector2(self.x, self.y) direction = 1 if self.seek else -1 #for flee distance *= direction steering = distance * self.speed - self.velocity steering = truncate(steering, self.max_force) self.velocity = truncate(self.velocity + steering, self.max_velocity) self.position += self.velocity * dt #actor 이동
전체 코드는 아래와 같다.
import cocos
import cocos.euclid as eu
import cocos.particle_systems as ps
def truncate(vector, m): # limites vector
magnitude = abs(vector) #크기 -> 루트 x^2+y^2
if magnitude > m: #크기가 m보다 크면 limit를 m으로 바꾸는
vector *= m / magnitude
return vector
class Actor(cocos.cocosnode.CocosNode):
def __init__(self, x, y):
super(Actor, self).__init__()
self.position = (x, y)
self.velocity = eu.Vector2(0, 0) #속도
self.speed = 1 #particle speed
self.max_force = 5
self.max_velocity = 200
self.target = None
self.seek = True
self.add(ps.Sun()) #particle system -> sun particle
self.schedule(self.update)
def update(self, dt): #update에서 target의 벡터값 계속 업데이트받음.
if self.target is None:
return
distance = self.target - eu.Vector2(self.x, self.y)
steering = distance * self.speed - self.velocity
steering = truncate(steering, self.max_force)
self.velocity = truncate(self.velocity + steering,
self.max_velocity)
direction = 1 if self.seek else -1 #for flee
self.position += self.velocity * dt * direction #actor 이동
#self.seek인 경우 1 방향, self.seek아닌 경우 -1 방향으로 이동
class MainLayer(cocos.layer.Layer):
is_event_handler = True
def __init__(self):
super(MainLayer, self).__init__()
self.actor = Actor(320, 240)
self.add(self.actor)
def on_mouse_motion(self , x, y, dx, dy):
self.actor.target = eu.Vector2(x, y) #actor의 target 벡터값을을 마우스 위치로
def on_mouse_press(self, x, y, buttons, mod):
self.actor.seek = not self.actor.seek #seek값 반대
if __name__ == '__main__':
cocos.director.director.init(caption='Steering Behaviors')
scene = cocos.scene.Scene(MainLayer())
cocos.director.director.run(scene)
Arrival
Arrival은 Seek와 같은 맥락인데 target과의 거리에 따라 해당 self.actor
의 self.velocity
값을 조절해주는 것이다.
self.slow_radius
와
ramp = min(abs(distance) / self.slow_radius, 1.0)
의 코드로 속도 벡터를 조절해줄 수 있다.
class Actor(cocos.cocosnode.CocosNode):
def __init__(self, x, y):
self.slow_radius = 200
def update(self, dt):
if self.target is None:
return
distance = self.target - eu.Vector2(self.x, self.y)
ramp = min(abs(distance) / self.slow_radius, 1.0)
# 전자가 1.0보다 크면, 1.0으로 제한함.
steering = distance * self.speed * ramp - self.velocity
steering = truncate(steering, self.max_force)
self.velocity = truncate(self.velocity + steering,
self.max_velocity)
self.position += self.velocity * dt
실행결과와 같이 타겟과의 거리가 가까워질수록 self
의 속도 벡터가 작아지는 값을 얻는다.
Pursuit and evade
이는 움직이는 타겟에 대한 추격과 도망이다. 현재 타겟의 위치를 받아서 그때마다 따라가는 것이 아니라, 일정 시간이 흐른 뒤 타겟이 움직임에 따라 미래에 있을 위치를 연산해서 그 방향으로 Seek
하는 방식이다.
def update(self, dt):
if self.target is None:
return
pos = self.target.position
future_pos = pos + self.target.velocity * dt
#기존 위치에 타겟이 향하는 속도 벡터 * 가변시간으로 미래 위치 얻어냄.
distance = future_pos - eu.Vector2(self.x, self.y)
steering = distance * self.speed - self.velocity
steering = truncate(steering, self.max_force)
self.velocity = truncate(self.velocity + steering,
self.max_velocity)
self.position += self.velocity * dt
움직이는 파란색 타겟에 대해 액터가 진행 방향으로 추격하는 모습이다.
Wander
임의로 무작위 랜덤 값을 받아서 움직인다. 이때 그냥 랜덤값을 받으면 부자연스러우므로 움직일 수 있는 거리에 원을 두고 원의 각도에 범위에 제한을 두도록 한다.
class Actor(cocos.cocosnode.CocosNode):
def __init__(self, x, y):
super(Actor, self).__init__()
self.position = (x, y)
self.velocity = eu.Vector2(0, 0)
self.wander_angle = 0 #초기 값
self.circle_distance = 50 #배회 거리에 해당하는 원
self.circle_radius = 10
self.angle_change = math.pi / 4 # 45 degree
self.max_velocity = 50
self.add(ps.Sun())
self.schedule(self.update)
이는 업데이트함수에서 구현을 하는데, 아래와 같다.
def update(self, dt):
circle_center = self.velocity.normalized() * \
self.circle_distance
# A, placin circle : self의 속도단위벡터 * 원과의 길이 배수
dx = math.cos(self.wander_angle)
dy = math.sin(self.wander_angle)
displacement = eu.Vector2(dx, dy) * self.circle_radius
# B, 랜덤으로 얻는 벡터 * 반지름만큼 배수
self.wander_angle += (random.random() - 0.5) * \
self.angle_change
#배회 각도 제한 : (랜덤 -0.5 ~ 0-.5 )* 45 => -22.5 ~ 22.5
self.velocity += circle_center + displacement
# A벡터 + B벡터의 합 -> Steering 속도 벡터
self.velocity = truncate(self.velocity,
self.max_velocity)
self.position += self.velocity * dt
self.position = (self.x % 640, self.y % 480)
방향이 무작위로 바뀌는 것이 아닌 일정 방향으로 자연스럽게 배회하는 것을 확인할 수 있다.
Obstacle avoidance
steering을 하다가 장애물이 있는 경우 장애물의 위치에서 방향을 피해서(seek
의 방향 반대로) 다시 steering을 하도록 하는데, 이때 장애물이 여러 개 있는 경우에는 self의 가장 가까운 범위 내에 있는 장애물에 대해 피하도록 루틴을 짜도록 한다.
import cocos
import cocos.euclid as eu
import cocos.particle_systems as ps
def truncate(vector, m):
magnitude = abs(vector)
if magnitude > m:
vector *= m / magnitude
return vector
class Obstacle(cocos.cocosnode.CocosNode):
instances = [] #static member
def __init__(self, x, y, r):
super(Obstacle, self).__init__()
self.position = (x, y)
self.radius = r
particles = ps.Sun()
particles.size = r * 2
particles.start_color = ps.Color(0.0, 0.7, 0.0, 1.0)
self.add(particles)
self.instances.append(self)
class Actor(cocos.cocosnode.CocosNode):
def __init__(self, x, y):
super(Actor, self).__init__()
self.position = (x, y)
self.velocity = eu.Vector2(0, 0)
self.speed = 2
self.max_velocity = 300
self.max_force = 10
self.target = None
self.max_ahead = 200
self.max_avoid_force = 300
self.add(ps.Sun())
self.schedule(self.update)
def update(self, dt):
if self.target is None:
return
distance = self.target - eu.Vector2(self.x, self.y)
steering = distance * self.speed - self.velocity
steering += self.avoid_force()
# 기존의 steering에 피하는 동작 추가
steering = truncate(steering, self.max_force)
self.velocity = truncate(self.velocity + steering,
self.max_velocity)
self.position += self.velocity * dt
def avoid_force(self):
avoid = eu.Vector2(0, 0)
ahead = self.velocity * self.max_ahead / self.max_velocity
# 진행하는 방향벡터 : 속도벡터 * 최대값 / 최대 속도
벡터
l = ahead.dot(ahead) #제곱
if l == 0:
return avoid #움직이는 경우에만 회피처리
closest, closest_dist = None, None
'''
#장애물 객체 for문처리 : 가장 가까운 장애물 찾음.
이후 기존의 진행 벡터에서 closet의 벡터를 빼서 avoid벡터를 만들고,
avoid벡터에 힘 크기만큼 곱해주어 avoid벡터를 수정해서 반환함.
'''
for obj in Obstacle.instances: #가장 가까운 벡터 찾음.
w = eu.Vector2(obj.x - self.x, obj.y - self.y)
t = ahead.dot(w)
if 0 < t < l: # l = a*a 진행방향에 어느정도 가까운 범위내에서만 고려
proj = self.position + ahead * t / l
dist = abs(obj.position - proj)
if dist < obj.radius and \
(closest is None or dist < closest_dist):
closest, closest_dist = obj.position, dist
if closest is not None:
avoid = self.position + ahead - closest
# 진행하려는 방향의 벡터 - closet 벡터
avoid = avoid.normalized() * self.max_avoid_force
# avoid 벡터 * 힘크기를 주어 벡터를 만듦
return avoid
class MainLayer(cocos.layer.Layer):
is_event_handler = True
def __init__(self):
super(MainLayer, self).__init__()
self.add(Obstacle(200, 200, 40))
self.add(Obstacle(240, 350, 50))
self.add(Obstacle(500, 300, 50))
self.actor = Actor(320, 240)
self.add(self.actor)
def on_mouse_motion(self, x, y, dx, dy):
self.actor.target = eu.Vector2(x, y)
if __name__ == '__main__':
cocos.director.director.init(caption='Steering Behaviors')
scene = cocos.scene.Scene(MainLayer())
cocos.director.director.run(scene)
Author And Source
이 문제에 관하여([게임 프로그래밍] Steering Behaviors), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@mmindoong/게임-프로그래밍-4.Steering-Behaviors저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)