22 Haziran 2013 Cumartesi

Vektörler

Şuana kadar cismimizi sabit yönlerde hareket ettirdik.Genelde hedef pozisyon belli değildi.Peki ya belli bir yönde hareket ettirmemiz gerekiyorsa ne yapacağız?Örneğin karakterimizin silahla önüne çıkan yaratıklara ateş ettiğini düşünelim.Kuşbakışı düşündüğümüzde merminin belli bir yönde gitmesi gerekiyor.Böyle bir durumda başlangıç koordinatıyla birlikte hedef koordinatını da bilmemiz lazım. Bu gibi durumlarda vektörlerden faydalanacağız.
pygame.math modülü içerisinde Vec2 isimli bir class var.Ama halen geliştirilme aşamasında.Bu yüzden kendi vektör sınıfımızı yazarak işe başlayalım.Sınıf kodumuz, python öğrenmeye yeni başlayanlar için biraz karışık gelebilir.Gerekli linkleri yazı içerisinde paylaşacağım.
#-*- coding:utf-8 -*-
#!/usr/bin/env python
import math

class Vector2(object):
 
 def __init__(self, x=0.0,y=0.0):
  self.x = x
  self.y = y

 def __str__(self):
  return "(%s, %s)"%(self.x, self.y)
 
 def get_coords(self):
  return (self.x, self.y)
 


 @staticmethod
 def from_points(P1,P2):
  return Vector2(P2[0] - P1[0], P2[1] - P1[1])

 def get_magnitude(self):
  return math.sqrt( self.x**2 + self.y**2 )
  
 def normalize(self):
  magnitude = self.get_magnitude()
  self.x /= magnitude
  self.y /= magnitude
 
 def __add__(self, vec):
  return Vector2(self.x + vec.x, self.y + vec.y)

 def __sub__(self, vec):
  return Vector2(self.x - vec.x, self.y - vec.y)


 def __neg__(self):
  return Vector2(-self.x, -self.y)

 def __mul__(self, scalar):
  return Vector2(self.x * scalar, self.y * scalar)
 
 def __div__(self, scalar):
  return Vector2(self.x / scalar, self.y / scalar)
from_points metodu verilen koordinatlara göre yeni bir Vector nesnesi döndürüyor.Burada farklı olarak @staticmethod isimli bir yapı var.Bu decorator, Java dilinde ki static niteleyicisiyle aynı işlevi görüyor.Static metodlar direkt class üzerinden erişilir.Bu metodu static yapmamızın sebebi, nesneye ait bir metod olmaması.Sadece verilen noktalara göre yeni bir Vector nesnesi oluşturması.Detaylı bilgi için:staticmethod & decorators

Vektör Uzunluğu 


get_magnitude,vektör uzunluğunu hesaplayan fonksiyonumuz.İki nokta arasındaki uzaklığı hesaplamamız gerektiğinde bunu kullanacağız.

Birim Vektör

Birim vektör, uzunluğu daima 1 br olan özel bir vektördür.Genellikle yön belirlemede kullanılır.normalize metodu vasıtasıyla bileşenleri magnitude değerine bölerek birim vektörümüzü hesaplıyoruz.

Vektörleri Toplama


 __add__ metodu, 2 vektörü toplayıp yeni bir vektör nesnesi döndürmektedir.Burada python'un bize sunduğu operator overloading özelliğini kullanıyoruz.Bu sayede add() şeklinde bir fonksiyon yaratıp AB.add(BC) şeklinde kullanmak yerine direkt AB + BC yazarak toplama işlemini gerçekleştirebileceğiz.
bknz:special method names

Bu şekilde kullanabileceğimiz diğer metodlar __sub__,__neg__,__mul__,__div__. Bunlarda sırasıyla çıkartma,negatifini alma,çarpma ve bölme.Mesela bulunduğumuz yönün tam tersine hareket etmemiz gerekiyorsa bu durumda yapacağımız vektörün negatifini almak.Bu şekilde aynı mesafede ama ters yönde hareket etmiş oluruz.

Şimdi vector sınıfımızı kullanarak bir uygulama yapalım:
 # -*- coding: utf-8 -*-

import pygame
from pygame.locals import *
from sys import exit

pygame.init()
sprite_image_filename = 'ball.png'
screen = pygame.display.set_mode((640, 480), 0, 32)

sprite = pygame.image.load(sprite_image_filename)
sprite = pygame.transform.scale(sprite,(30,30))
clock = pygame.time.Clock()
position = Vector2(100.0, 100.0)
speed = 250.
heading = Vector2()

while True:
 for event in pygame.event.get():
  if event.type == QUIT:
   exit()
  if event.type == MOUSEBUTTONDOWN:
   destination = Vector2(*event.pos) - Vector2(*sprite.get_size())/2.0
   heading = Vector2.from_points(position.get_coords(), destination.get_coords())
   heading.normalize()
 
 screen.fill((0,0,0))
 screen.blit(sprite, position.get_coords())
 time_passed = clock.tick()
 time_passed_seconds = time_passed / 1000.0
 distance_moved = time_passed_seconds * speed
 position += heading * distance_moved
 pygame.display.update()

Uygulama ekrana bir top nesnesi yerleştiriyor ve faremizin sol tuşuna tıkladığımızda nesnemiz cursor'a doğru hareket etmeye başlıyor.Tıklamamıza bağlı olarak sürekli cursor'un bulunduğu konuma yöneliyor.Şuan için event kısmını görmezden gelebilirsiniz.Kodumuzu biraz açalım.

İlk başta pozition ve heading-yön vektörümüz- yaratıyoruz.Yön vektörümüzün oluşma olayını ise farenin sol tuşuyla tetikliyoruz.
 
destination = Vector2(*event.pos) - Vector2(*sprite.get_size())/2.0
heading = Vector2.from_points(position.get_coords(), destination.get_coords())
heading.normalize()
Burada hedef vektörümüzü-destination- hesaplamak için cursor'un bulunduğu konumdan -event.pos- resmimizin merkezini hedef alan vektörü çıkartıyoruz.Bu şekilde hedef koordinatımızı belirlemiş olduk.Bu koordinatları kullanarak heading-birim vektör-oluşturuyoruz.En son normalize ettiğimizde birim vektörümüz hazır durumda.

Burada birşey dikkatinizi çekmiştir.
 
destination = Vector2(*event.pos) - Vector2(*sprite.get_size())/2.0
Buradaki  işareti girilecek parametre sayısını genişletmeye yarıyor.Mesela
 def func(*args):
dediğimizde, fonksiyona gönderilecek parametre sayısının belli olmadığını belirtiyoruz.Yani
Vector2(event.pos[0], event.pos[1])  ile Vector2(*event.pos) aynı şeyi ifade ediyor.Eğer hedef koordinatları uzun yoldan hesaplamak isteseydik:
destination_x = event.pos[0] – sprite.get_width()/2.0
destination_y = event.pos[1] – sprite.get_height()/2.0
destination = (destination_x, destination_y)
bu şekilde yazmamız gerekecekti.
Son olarak oyun döngümüzde speed * time_passed_seconds ile nesnenin ne kadar yol aldığını hesaplayıp heading vektörü ile çarpıyoruz.Son elde ettiğimiz vektör, pozisyonumuzdaki değişikliği bize veriyor.Dikkat ettiyseniz time-based hareket kullandık.Bundan sonraki bütün uygulamalarımızı bu şekilde yapacağız.
Kaynakça:

0 yorum :

Yorum Gönder