[CLANAK 6.] - Game mechanics 2: Upotreba vektora i trigonome

Koki007 nas uvodi u svijet Pythona i PyGamea!

Moderator: Danijel Korent

Post Reply
User avatar
Danijel Korent
Romulanski špijun na Zemlji
Posts: 1583
Joined: Fri Sep 05, 2008 12:35 pm
Smallest prime number bigger than 20: 23
Location: Njemacka
Contact:

[CLANAK 6.] - Game mechanics 2: Upotreba vektora i trigonome

Post by Danijel Korent » Sun Dec 05, 2010 10:18 pm

6. GAME MECHANICS 2. - Upotreba vektora i trigonometrije u igrama

6.1. UVOD

Ovdje cu (malo) pisati o vektorima i trigonometriji, i naravno o njihovoj upotrebi u igrama. Da unaprijed upozorim - uopce ne kanim ici u sirinu i dubinu ovih pojmova, fokusirat cu se na neke konkretne probleme koje susrecemo kod programiranja igara i objasnit cu onoliko matematike koliko je potrebno da se ti problemi rijese, te cu pokusati cim vise "zdravoseljacki" objasniti, sa cim manje nekih matematickih izraza, definicija i formula, dakle matematika ostaje u matematickim udzbenicima.


6.2. PRIMJER SIMULIRANJA GRAVITACIJE IZMEDU SVEMIRSKIH TIJELA

U ovom primjeru ce se najlakse prikazati upotreba i uloga vektora u igrama. Vektori su najednostavnije receno matematicki objekt odreden smjerom i magnutudom iliti duzinom. Znaci sam vektor sadrzi dvije informacije, smjer u koji "gleda" i duzinu. U igrama cesto to koristimo, da odredimo u kojem smjeru ce se neki objekt kretati i za koliko ce se pomaknuti. Moze se definirati sa dvije tocke u nekom koordinatnom sustavu, pocetna tocka i druga tocka koja "odreduje smjer", a udaljenost izmedu te dvije tocke je magnutuda/duzina vektora, ili sa samo jednom tockom, pri cemu se podrazumijeva da je pocetna tocka na koordinatama (0,0) odnosno u centru koordinatnog sustava.

Primjer ce crtati dva tijela, zvijezdu i planet, pri cemu ce planet orbitirati oko zvijezde. Kako to postici? Prvo treba izracunati udaljenost izmedu planeta i zvijezde da bi se moglo dalje izracunati koliko jako zvijezda gravitacijski djeluje na taj planet u tom trenutku, te treba izracunati smjer vektora te gravitacijske sile, koji je naravno uvijek usmjeren od centra planeta prema centra zvijezde. Kad smo dobili jacinu sile i smjer sile, imamo vektor sile koja djeluje na planet. Takoder se planet i krece, tako da imamo neki smjer kretanja i brzinu kretanja, sto opet cini vektor kretanja planete. Da bi dobili rezultat gravitacijkog djelovanja na planet moramo zbrojiti ta dva vektora, vektor kretanja i vektor silu gravitacije koja djeluje na planet, dobiveni vektor nam je novi vektor kretanja planete. Pa krenimo:

Prvo cemo nacrtati ova dva objekta i pokrenuti planet:
Spoiler! :

Code: Select all

#! /usr/bin/env python
# -*- coding: utf-8 -*-

import pygame
from pygame.locals import *
import os

pygame.init()
os.environ['SDL_VIDEO_CENTERED'] = '1'

glavni_buffer = pygame.display.set_mode((800, 600), DOUBLEBUF)

zvijezda_PozicijaX = 400
zvijezda_PozicijaY = 300
#zvijezda_BrzinaX = 1
#zvijezda_BrzinaY = 1
zvijezda_slika = pygame.image.load('sun64_roza.bmp').convert()

planet_PozicijaX = 200
planet_PozicijaY = 300
planet_BrzinaX = 1
planet_BrzinaY = -1
planet_slika = pygame.image.load('mars32_roza.bmp').convert()


# funkcija za crtanje vektora - nebitna za ovu pricu, glavno da crta :D
def nacrtaj_vektor(abs1 = 0, abs2 = 0, poz = 0, rel = 0, duzina = 1):
    if abs1 and abs2:
        tocka2 = ( (abs2[0] - abs1[0]) * duzina, (abs2[1] - abs1[1]) * duzina) 
        pygame.draw.line(glavni_buffer, (255,0,0), abs1, abs2 )
    elif poz and rel:
        tocka2 = ( poz[0] + rel[0] * duzina, poz[1] + rel[1] * duzina)
        pygame.draw.line(glavni_buffer, (255,0,0), poz, tocka2 )
    else: print "greska kod crtanja vektora"
    pass





glavna_petlja = True

tajmer = pygame.time.Clock()

while glavna_petlja == True:

    # zatvaranje prozora ako se pritisne ESC ili gumb X u uglu prozora
    for event in pygame.event.get():
        if event.type == QUIT:
            glavna_petlja = False

        if event.type == KEYDOWN:
            if event.key == K_ESCAPE:
                glavna_petlja = False
                
                
    # kretanje planete
    planet_PozicijaX = planet_PozicijaX + planet_BrzinaX
    planet_PozicijaY = planet_PozicijaY + planet_BrzinaY

    
    # crtanje zvijezde i planete
    glavni_buffer.fill((0, 0, 0))
    glavni_buffer.blit(zvijezda_slika, (zvijezda_PozicijaX, zvijezda_PozicijaY))
    glavni_buffer.blit(planet_slika, (planet_PozicijaX, planet_PozicijaY))

    
    #crtanje vektora
    nacrtaj_vektor( poz =(planet_PozicijaX, planet_PozicijaY), rel = (planet_BrzinaX, planet_BrzinaY), duzina = 30 )
    nacrtaj_vektor( abs1 =(planet_PozicijaX, planet_PozicijaY), abs2 = (zvijezda_PozicijaX, zvijezda_PozicijaY) )
    
    pygame.display.flip()  
    tajmer.tick(60) # ogranicavanje brzine izvodenja igre

Vektor kretanja planeta je odreden samo jednom tockom (planet_BrzinaX, planet_BrzinaY) jer koordinate nisu absolutne nego je druga tocka izrazena pozicijom relativno od sredista planeta, znaci prva tocka vektora je centar planeta, a ako stavimo planet u srediste koordinatnog sustava koordinate te tocke su (0, 0) pa ih nema smisla pamtiti.

Takoder sam stavio da se nacrta vektor kretanja planeta (uvecan 10x da ipak bude veci od jednog-dva pixela) i vektor od planeta do Sunca. Kao sto se i vidi na slici, vektor ne krece od sredine planeta nego od lijevog gornjeg slike slike planeta, zasto? On se crta od koordinata planeta zadanih varijablama planet_PozicijaX i Y, te sliku planeta isto crtamo na te iste koordinate. Buduci da se slika crta od gornjeg lijevog kuta to onda tako izgleda, da bi na tim koordinatama bio centar planete kao sto je i zamisljeno, sliku planete treba pomaknuti lijevo i gore za polovicu svoje sirine/visine, pa da prepravim to crtanje:

Code: Select all

glavni_buffer.blit(zvijezda_slika, (zvijezda_PozicijaX - zvijezda_slika.get_width() / 2, zvijezda_PozicijaY - zvijezda_slika.get_height() / 2))
    glavni_buffer.blit(planet_slika, (planet_PozicijaX - planet_slika.get_width() / 2, planet_PozicijaY - planet_slika.get_height() / 2))

Krenimo sa dobivanjem vektora sile gravitacije koja djeluje na planet. Prvo cemo dodati jos jednu varijablu, koliko jako zvijezda gravitacijski utjece na planete, odnosno kolika joj je masa pa dodajem ovu varijablu:

Code: Select all

zvijezda_masa = 500
Prvo cemo izracunati koliko jako se na planet djeluje gravitacijski. Jacinu gravitacijskog djelovanja zvijezde imamo, i ona slabi proporcionalno sa kvadratom udaljenosti od centra zvijezde, znaci formula zvuci ovako:

masa_zvijezde / udaljenost_planet_zvijezda * udaljenost_planet_zvijezda

Masu zvijezde imamo, sada samo treba izracunati udaljenost izmedu zvijezde i planeta, sa formulom za racunanje udaljenosti izmedu dvije tocke: (http://www.purplemath.com/modules/distform.htm" onclick="window.open(this.href);return false;)

d = sqrt( (x2 - x1)** + (y2 - y1)** )

Uvrstimo to u nas kod:

Code: Select all

    udaljenost_planet_sunce =  math.sqrt( (planet_PozicijaX - zvijezda_PozicijaX)**2 + (planet_PozicijaY - zvijezda_PozicijaY)**2 )
    sila_gravitacije = zvijezda_masa / udaljenost_planet_sunce**2
sad kad imamo magnitudu/jacinu sile, trebamo jos samo smjer da to ukombiniramo u jedan vektor. Prvo cemo uzeti vektor iz tocaka planet->zvijezda, znaci pocetna tocka je planet a zavrsna zvijezda, te onda premjestimo vektor da je pocetna tocka na koordinatama (0,0) te ga prikazemo sa jednom tockom:

Code: Select all

    vektor_planet_zvijezda_X = zvijezda_PozicijaX - planet_PozicijaX
    vektor_planet_zvijezda_Y = zvijezda_PozicijaY - planet_PozicijaY
Sad imamo vektor sa dobrim smjerom ali mu je magnutida/duzina iznosi udaljenost izmedu planeta i zvijezde, a za nas vektor sile magnituda/duzina tog vektora mora biti iznos jacine sile. Da bi to postigli skratit cemo vektor da bude tocno duljine jedan (koji se i zove unit vektor - ne mogu se sjetiti kak se kaze na hrv O.o), to se postize tako da se vektor podijeli sa svojom vlastitom magnitudom/duzinom. Nakon toga taj vektor pomnozimo sa vrijednoscu izracunate sile gravitacije da taj vektor bude te duzine:

Vektor cemo pomnoziti tako sto cemo mu i x i y komponentu pomnoziti za neku vrijednost, isto tako cemo i podijeliti:

vektor_x = vektor_x / neka_vrijednost
vektor_y = vektor_y / neka_vrijednost

vektor_x = vektor_x * neka_vrijednost
vektor_y = vektor_y * neka_vrijednost


i ovako to izgleda u kodu primjera:

Code: Select all

    vektor_planet_zvijezda_X = vektor_planet_zvijezda_X / udaljenost_planet_sunce
    vektor_planet_zvijezda_Y = vektor_planet_zvijezda_Y / udaljenost_planet_sunce
    
    vektor_sila_gravitacije_X = vektor_planet_zvijezda_X * sila_gravitacije
    vektor_sila_gravitacije_Y = vektor_planet_zvijezda_Y * sila_gravitacije
Sad kad imamo vektor sile koja djeluje na planet, zbrojimo ga sa vektorom kretanja planeta da bi dobili novi vektor kretanja promjenjen za jacinu i smjer utjecanja gravitacije na njega:

Code: Select all

    planet_BrzinaX = planet_BrzinaX + vektor_sila_gravitacije_X
    planet_BrzinaY = planet_BrzinaY + vektor_sila_gravitacije_Y

I to je to, nas planet je sada u orbiti oko zvijezde! A za postimati u kakvoj ce orbiti biti, treba se igrati sa raznim parametrima, masom zvijezde i pozicijom, smjerom i brzinom planete. Ako ne treba bas simulacija gravitacije nego je dovoljna simulacija kruznog kretanja onda je to puno jednostavnije napraviti pomocu sinus i cosinus funkcija, koje ce biti objasnjene malo u iducim tockama clanka.
Spoiler! :

Code: Select all

#! /usr/bin/env python
# -*- coding: utf-8 -*-

import pygame
from pygame.locals import *
import math
import os

pygame.init()
os.environ['SDL_VIDEO_CENTERED'] = '1'

glavni_buffer = pygame.display.set_mode((800, 600), DOUBLEBUF)

zvijezda_PozicijaX = 400
zvijezda_PozicijaY = 300
#zvijezda_BrzinaX = 1
#zvijezda_BrzinaY = 1
zvijezda_slika = pygame.image.load('sun64.bmp').convert()
zvijezda_masa = 500

planet_PozicijaX = 200
planet_PozicijaY = 300
planet_BrzinaX = 1
planet_BrzinaY = -1
planet_slika = pygame.image.load('mars32.gif').convert()


# funkcija za crtanje vektora - nebitna za ovu pricu, glavno da crta :D
def nacrtaj_vektor(abs1 = 0, abs2 = 0, poz = 0, rel = 0, duzina = 1):
    if abs1 and abs2:
        tocka2 = ( (abs2[0] - abs1[0]) * duzina, (abs2[1] - abs1[1]) * duzina) 
        pygame.draw.line(glavni_buffer, (255,0,0), abs1, abs2 )
    elif poz and rel:
        tocka2 = ( poz[0] + rel[0] * duzina, poz[1] + rel[1] * duzina)
        pygame.draw.line(glavni_buffer, (255,0,0), poz, tocka2 )
    else: print "greska kod crtanja vektora"
    pass





glavna_petlja = True

tajmer = pygame.time.Clock()

while glavna_petlja == True:

    # zatvaranje prozora ako se pritisne ESC ili gumb X u uglu prozora
    for event in pygame.event.get():
        if event.type == QUIT:
            glavna_petlja = False

        if event.type == KEYDOWN:
            if event.key == K_ESCAPE:
                glavna_petlja = False
            
    # kretanje planete
    planet_PozicijaX = planet_PozicijaX + planet_BrzinaX
    planet_PozicijaY = planet_PozicijaY + planet_BrzinaY

    
    # racunanje vektora sile gravitacije koja utjece na planet:
    udaljenost_planet_sunce =  math.sqrt( (planet_PozicijaX - zvijezda_PozicijaX)**2 + (planet_PozicijaY - zvijezda_PozicijaY)**2 )
    sila_gravitacije = zvijezda_masa / udaljenost_planet_sunce**2
    
    vektor_planet_zvijezda_X = zvijezda_PozicijaX - planet_PozicijaX
    vektor_planet_zvijezda_Y = zvijezda_PozicijaY - planet_PozicijaY
    
    vektor_planet_zvijezda_X = vektor_planet_zvijezda_X / udaljenost_planet_sunce
    vektor_planet_zvijezda_Y = vektor_planet_zvijezda_Y / udaljenost_planet_sunce
    
    vektor_sila_gravitacije_X = vektor_planet_zvijezda_X * sila_gravitacije
    vektor_sila_gravitacije_Y = vektor_planet_zvijezda_Y * sila_gravitacije
    
    
    # zbrajanje vektora kretanja planete i vektora gravitacijske sile koja djeluje na planet
    planet_BrzinaX = planet_BrzinaX + vektor_sila_gravitacije_X
    planet_BrzinaY = planet_BrzinaY + vektor_sila_gravitacije_Y
    
    
    # crtanje zvijezde i planete
    glavni_buffer.fill((0, 0, 0))
    glavni_buffer.blit(zvijezda_slika, (zvijezda_PozicijaX - zvijezda_slika.get_width() / 2, zvijezda_PozicijaY - zvijezda_slika.get_height() / 2))
    glavni_buffer.blit(planet_slika, (planet_PozicijaX - planet_slika.get_width() / 2, planet_PozicijaY - planet_slika.get_height() / 2))

    
    #crtanje vektora
    nacrtaj_vektor( poz =(planet_PozicijaX, planet_PozicijaY), rel = (planet_BrzinaX, planet_BrzinaY), duzina = 50 )
    nacrtaj_vektor( poz =(planet_PozicijaX, planet_PozicijaY), rel = (vektor_sila_gravitacije_X, vektor_sila_gravitacije_Y), duzina = 150)
    
    pygame.display.flip()  
    tajmer.tick(60) # ogranicavanje brzine izvodenja igre

6.3. PRIMJER ROTIRANJA NEKOG OBJEKTA I KRETANJA U SMJERU PREMA KOJEM JE ZAROTIRAN

U ovom primjeru zadatak ce biti nacrtati brodic koji ce "gledati" u nekom zadanom smjeru i ujedno kretat se u tom smjeru, izgleda lagano. Ako ste malo proucili pygame i njegove mogucnosti, vec znate da slike koje ucitate mozete i zarotirati (kao i u mnogim drugim slicnim library-ima), sto je jako zgodno ako zelite da vam lik u igri gleda u nekom odredenom smjeru.

Pa da krenem, prvo cu nacrati brod na lijepoj plavoj pozadini (pozicija x i y je opet u centru pa tako i crtamo), zarotirat ga za neki kut i staviti da se krece po nekom vektoru kretanja:
Spoiler! :

Code: Select all

#! /usr/bin/env python
# -*- coding: utf-8 -*-

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

pygame.init()
os.environ['SDL_VIDEO_CENTERED'] = '1'

glavni_buffer = pygame.display.set_mode((800, 600), DOUBLEBUF)

brod_PozicijaX = 400
brod_PozicijaY = 300
brod_KretanjeX = -0.3
brod_KretanjeY = -0.2
brod_Kut = 60

brod_Slika = pygame.image.load('brod.png').convert()

glavna_petlja = True

tajmer = pygame.time.Clock()
MisX = 0
MisY = 0


while glavna_petlja == True:

    for event in pygame.event.get():
		
        if event.type == QUIT:
            exit()

        if event.type == KEYDOWN:
            if event.key == K_ESCAPE:
                glavna_petlja = False
                
        if event.type == MOUSEMOTION:
            MisX = event.pos[0]
            MisY = event.pos[1]

    # kretanje broda, odnosno racunanje nove pozicije na temelju vektora kretanja
    brod_PozicijaX = brod_PozicijaX + brod_KretanjeX
    brod_PozicijaY = brod_PozicijaY + brod_KretanjeY
    
    #rotiranje osnovne slike broda (one koja gleda "prema" 0 stupnjeva)
    brod_zarotirani = pygame.transform.rotate(brod_Slika, brod_Kut)
    
    # crtanje pozadine i broda
    glavni_buffer.fill((18, 176, 227)) # pozadina je plava
    glavni_buffer.blit( brod_zarotirani, (brod_PozicijaX - brod_zarotirani.get_width() / 2, brod_PozicijaY - brod_zarotirani.get_height() / 2) )
    pygame.display.flip()  
    tajmer.tick(60)
Evo to je napravljeno, brod je zarotirani za neki kut, i krece se prema nekom smjeru. Jedini je problem sto se brod ne krece u smjeru u kojem gleda. Pa kako to postici?

Evo malo vizualnog prikaza problema:
Image

Na slici imamo nacrtani neki vektor nazvan X koji gleda u neki smjer, i koji sa x-osi tvori/zatvara kut nazvan A. Ako nacrtani brod prilikom nula stupnjeva (znaci prije ikakve rotacije) gleda tocno desno, tj. "gleda" u smjeru vektora koji je potpuno poravnan sa pozitivnom x-osi (nula stupnjeva), onda ako se zarotira za kut A on gleda u smjeru vektora X, znaci mi moramo izracunati vektor X da bi znali u kojem smjeru se brod teba kretati. U ovom primjeru vrijednost kuta uvijek znamo te trebamo za neku vrijednost kuta dobiti koordinate tog vektora , a buduci da vektor pocinje na (0,0) trebamo samo doznati vrijednsti x i y. Za to cemo upotrijebiti trigonometrijske funkcije sinus i cosinus, na ovaj nacin:

x = cos(kut)
y = sin(kut)

Ovime dobivamo unit vektor (vektor duzine/magnitude 1) koji gleda tocno u taj smjer. U pythonu se koriste tako da se importa modul math koji ima funkcije sin(kut) i cos(cos). Evo jedan primjer koji to demonstrira:
Spoiler! :

Code: Select all

#! /usr/bin/env python
# -*- coding: utf-8 -*-

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

pygame.init()
os.environ['SDL_VIDEO_CENTERED'] = '1'

glavni_buffer = pygame.display.set_mode((800, 600), DOUBLEBUF)

glavna_petlja = True
tajmer = pygame.time.Clock()

kut = 0
vrijeme = pygame.time.get_ticks()
font = pygame.font.Font( None, 42)

while glavna_petlja == True:

    for event in pygame.event.get():
	
        if event.type == QUIT:
            glavna_petlja = False

        if event.type == KEYDOWN:
            if event.key == K_ESCAPE:
                glavna_petlja = False
                
    # svaku sekundu poveca kut 
    if pygame.time.get_ticks() - vrijeme > 1000:
        vrijeme = pygame.time.get_ticks()
        kut = kut + 15


    glavni_buffer.fill((0, 0, 0))
    
    #crta x i y os
    pygame.draw.line(glavni_buffer, (255, 0, 0), (0, 300), (800, 300))
    pygame.draw.line(glavni_buffer, (255, 0, 0), (400, 0), (400, 600))
                
                
  
    # prvo pretvaramo iznos kuta iz stupnjeva u radijane jer 
    # sin i cos u pythonu primaju vrijednosti u radijanima
    kut_radian = math.radians(kut) 
    
    # racunanje vektora
    vectX = math.cos(kut_radian)
    vectY = -math.sin(kut_radian)
    
    
    
    #povecavanje vektora
    vectX = vectX * 100
    vectY = vectY * 100

    #crtanje vektora
    pygame.draw.line(glavni_buffer, (0, 255, 0), (400, 300), (vectX + 400, vectY + 300))
    
    #crta iznos kuta u desnom gornjem uglu
    font_surface = font.render( str(kut), 1, (255, 0, 0))
    glavni_buffer.blit( font_surface, (700, 10) )
    
    pygame.display.flip()  
    tajmer.tick(60)

A sad da tu formulu uvrstim i u primjer sa brodom:

Code: Select all

    import math

    brod_KretanjeX = math.cos(kut_radian)
    brod_KretanjeY = math.sin(kut_radian)
Ovo iz nekog razloga ne radi dobro, a razlog je sto vrijednost kuta saljemo u stupnjevima a ove funkcije zahtjevaju kut izrazen u radianima, pa moramo prvo vrijednosti kuta promjenit jedinicu mjere iz stupnjeva u radiane, za sto ce nam posluziti metoda radians(kut) iz math modula:

Code: Select all

    kut_radian = math.radians(brod_Kut)
    
    brod_KretanjeX = math.cos(kut_radian)
    brod_KretanjeY = math.sin(kut_radian)

Sada je vec bolje, ali se brod krece prema dolje umjesto prema gore. Razlog tome je sto u nasem onom nacrtanom primjeru pozitivna y os gleda prema gore, znaci cim je veca y vrijednost tocka je sve vise, dok kod pygame-a pozitivna y os gleda prema dolje, znaci cim je veca y vrijednost tocka je sve nize. Zbog toga cemo y vrijednosti kod vektora dodati minus da "preokrenemo" tu os:

Code: Select all

    kut_radian = math.radians(brod_Kut)
    
    brod_KretanjeX = math.cos(kut_radian)
    brod_KretanjeY = -math.sin(kut_radian)


Sad se brod krece u dobrom smjeru ali uvijek iste brzine (vektor je uvijek velicine/magnitude 1), a obicno zelimo imati mogucnost kontrole brzine. Znaci vektor kretanja broda treba povecati da mu vrijednost magnitude bude jednaka toj vrijednosti brzine. TO naravno radimo tako da pomnozimo vektor kretanja broda za neku vrijednost:

Code: Select all

    kut_radian = math.radians(brod_Kut)
    
    brod_KretanjeX = math.cos(kut_radian) * brod_Brzina
    brod_KretanjeY = -math.sin(kut_radian) * brod_Brzina
Eto to je to, sada se brod uvijek krece u smjeru prema kojem gleda, odnosno kamo je zarotiran, i to brzinom koju mu zadamo. Evo puni kod:
Spoiler! :

Code: Select all

#! /usr/bin/env python
# -*- coding: utf-8 -*-

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

pygame.init()
os.environ['SDL_VIDEO_CENTERED'] = '1'

glavni_buffer = pygame.display.set_mode((800, 600), DOUBLEBUF)

brod_PozicijaX = 400
brod_PozicijaY = 300
#brod_KretanjeX = -0.3
#brod_KretanjeY = -0.2
brod_Kut = 60
brod_Brzina = 2

brod_Slika = pygame.image.load('brod.png').convert()

glavna_petlja = True

tajmer = pygame.time.Clock()

while glavna_petlja == True:

    for event in pygame.event.get():
		
        if event.type == QUIT:
            exit()

        if event.type == KEYDOWN:
            if event.key == K_ESCAPE:
                glavna_petlja = False
                

    #racunanje vektora kretanja na temelju smjera(kuta) broda i brzine
    kut_radian = math.radians(brod_Kut)
    
    brod_KretanjeX = math.cos(kut_radian) * brod_Brzina
    brod_KretanjeY = -math.sin(kut_radian) * brod_Brzina
                
    # kretanje broda, odnosno racunanje nove pozicije na temelju vektora kretanja
    brod_PozicijaX = brod_PozicijaX + brod_KretanjeX
    brod_PozicijaY = brod_PozicijaY + brod_KretanjeY
    
    #rotiranje osnovne slike broda (one koja gleda "prema" 0 stupnjeva)
    brod_zarotirani = pygame.transform.rotate(brod_Slika, brod_Kut)
    
    # crtanje pozadine i broda
    glavni_buffer.fill((18, 176, 227)) # pozadina je plava
    glavni_buffer.blit( brod_zarotirani, (brod_PozicijaX - brod_zarotirani.get_width() / 2, brod_PozicijaY - brod_zarotirani.get_height() / 2) )
    pygame.display.flip()  
    tajmer.tick(60)
6.4. PRIMJER OKRETNJA IGRACA PREMA KOORDINATAMA MISA

Okretanje nekog objekta prema nekoj tocki je cesto potrebno u igri, kao u ovom primjeru okretanja igraca prema koordinatama misa ili npr. okretanje neprijatelja prema igracu i sl. Da bi okrenuli igraca prema misu trebamo znati za koji kut treba igraca okrenuti, a raspolazemo informacijom o polozaju igraca i polozaju misa, iz cega mozemo uzeti vektor smjera prema kojeg igrac treba gledati. Znaci sada imamo situaciju obratnu u odnosu na prosli primjer, imamo neki vektor i trebamo iz njega dobiti kut za koji treba zarotirati objekt da gleda u smjeru tog vektora.

Evo jos jednom one slike kuta:
Image

da bi dobili kut A iz vektora X (tocnije iz njegovoh tocaka x, y) koristimo trigonometrijsku funkciju atan2, ovako:

kut = atan2(y, x)

vraceni kut je u radijanima te ga (najcesce) treba pretvoriti u stupnjeve, a x i y su koordinate vektora (kojem je pocetna tocka (0, 0) ). Btw primjetite da se prvo pise y a onda x koordinata, na to treba paziti jer brzopleti (kao npr. ja) stave prvo x,y kao sto je i uobicajeno pa se cude dobivenim rezulatom. Takoder opet ispred y treba staviti minus ako zelimo da nam u pygameu objekt ne gleda prema dolje umjesto prema gore i obratno.

Pa evo i primjer koji je modificirani primjer_2 iz proslog poglavlja, koji za neki vektor izmedu cetra ekrana i misa vraca kut izmedu tog vektora i x osi:
Spoiler! :

Code: Select all

#! /usr/bin/env python
# -*- coding: utf-8 -*-

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

pygame.init()
os.environ['SDL_VIDEO_CENTERED'] = '1'

glavni_buffer = pygame.display.set_mode((800, 600), DOUBLEBUF)

glavna_petlja = True
tajmer = pygame.time.Clock()

kut = 0
vrijeme = pygame.time.get_ticks()
font = pygame.font.Font( None, 42)

MisX = 0
MisY = 0


while glavna_petlja == True:

    for event in pygame.event.get():
	
        if event.type == QUIT:
            glavna_petlja = False

        if event.type == KEYDOWN:
            if event.key == K_ESCAPE:
                glavna_petlja = False

        #dobivanje koordinata misa i spremanje u varijable MisX i MisY
        if event.type == MOUSEMOTION:
            MisX = event.pos[0]
            MisY = event.pos[1]
            
            

    #brisemo prozor prije crtanja
    glavni_buffer.fill((0, 0, 0))
    
    #crta x i y os
    pygame.draw.line(glavni_buffer, (255, 0, 0), (0, 300), (800, 300))
    pygame.draw.line(glavni_buffer, (255, 0, 0), (400, 0), (400, 600))
                
    #dobivanje vektora u odnosu na centar prozora
    vectX = MisX - 400
    vectY = MisY - 300
    
    #racunanje kuta izmedu dobivenog vektora i x-osi
    kut = math.atan2(-vectY, vectX)
    kut = math.degrees(kut)

    
    #crtanje vektora
    pygame.draw.line(glavni_buffer, (0, 255, 0), (400, 300), (vectX + 400, vectY + 300))
    
    #crta iznos kuta u desnom gornjem uglu
    font_surface = font.render( str(kut), 1, (255, 0, 0))
    glavni_buffer.blit( font_surface, (700, 10) )
    
    # update prozora nakon svih crtanja
    pygame.display.flip()  
    
    #ogranicavanje frameratea na 60fps-a
    tajmer.tick(60)

Pa da to iskoristim u primjeru okretanja igraca. Prvi primjer iz proslog poglavlja ce posluziti kao startna tocka, samo se doda uzimanje koordinata misa koje ce biti pamcene u varijablama MisX i MisY. Prvo ste se treba napraviti je dobiti vektor smjera prema kojem igrac treba gledati, to je vektor od tocke pozicije igraca do tocke pozicije misa, te cemo ga premjestiti da mu je srediste na koordinatama (0,0) tako da pamtimo samo jednu tockom, jer nam pozicija vektora ovdje nije bitna samo smjer:

Code: Select all

    smjerX = MisX - brod_PozicijaX
    smjerY = MisY - brod_PozicijaY

sad kad imam vektor upotrijebit cu atan2 funkciju, rezulatat pretvoriti u stupnjeve i postaviti dobivaenu vrijednost za vrijednost kuta broda:

Code: Select all

    kut = math.atan2( -smjerY, smjerX)
    kut = math.degrees(kut)
    brod_Kut = kut
To je to, brod se sada uvijek okrece prema misu! Da ne bude tako statican preuredit ce ono kretanje da se krece u smjeru vektora brod->mis, pomnozen sa brzinom:

Prvo sto moramo napraviti je normalizirati (smanjit na duzinu/magnitudu 1) onaj vektor smjera od prije jer je trenutno duzina vektora onolika kolika ja udaljenost izmedu broda i misa, a to nama ne odogvara ako zelimo vektor pomnozoto za neku brzinu, ako se sjecate prvog poglavlja to se radi tako da se duzina vektora podijeli za samim vektorom:

Code: Select all

    magnutuda_vektora_smjera = math.sqrt( smjerX**2 + smjerY**2 )
    smjerX = smjerX / magnutuda_vektora_smjera
    smjerY = smjerY / magnutuda_vektora_smjera

Nakon toga mnozimo taj vektor za zadanu brzinu i azuriramo novu poziciju

Code: Select all

    brod_PozicijaX = brod_PozicijaX + smjerX * brod_Brzina
    brod_PozicijaY = brod_PozicijaY + smjerY * brod_Brzina
Eto, sada brodic neprestano juri za kursorom misa, i time ovo poglavlje zavrsava! Evo kompletan kod primjera:
Spoiler! :

Code: Select all

#! /usr/bin/env python
# -*- coding: utf-8 -*-

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

pygame.init()
os.environ['SDL_VIDEO_CENTERED'] = '1'

glavni_buffer = pygame.display.set_mode((800, 600), DOUBLEBUF)


brod_PozicijaX = 400
brod_PozicijaY = 300
brod_KretanjeX = 0
brod_KretanjeY = 0
brod_Kut = 60
brod_Brzina = 2

brod_Slika = pygame.image.load('brod.png').convert()

glavna_petlja = True

tajmer = pygame.time.Clock()

MisX = 0
MisY = 0
   


while glavna_petlja == True:

    for event in pygame.event.get():
		
        if event.type == QUIT:
            exit()

        #dobivanje koordinata misa i spremanje u varijable MisX i MisY
        if event.type == MOUSEMOTION:
            MisX = event.pos[0]
            MisY = event.pos[1]
 
    
    #dobivanje vektora izmedu broda i misa u odnosu na poziciju broda
    smjerX = MisX - brod_PozicijaX
    smjerY = MisY - brod_PozicijaY
    
    #racunanje kuta iz dobivenog vektora
    kut = math.atan2( -smjerY, smjerX)
    kut = math.degrees(kut)
    brod_Kut = kut
    

    # kretanje broda, odnosno racunanje nove pozicije na temelju vektora kretanja
    magnutuda_vektora_smjera = math.sqrt( smjerX**2 + smjerY**2 )
    smjerX = smjerX / magnutuda_vektora_smjera
    smjerY = smjerY / magnutuda_vektora_smjera
    
    brod_PozicijaX = brod_PozicijaX + smjerX * brod_Brzina
    brod_PozicijaY = brod_PozicijaY + smjerY * brod_Brzina
    
    
    
    #rotiranje osnovne slike broda (one koja gleda "prema" 0 stupnjeva)
    brod_zarotirani = pygame.transform.rotate(brod_Slika, brod_Kut)
    
    # crtanje pozadine i broda
    glavni_buffer.fill((18, 176, 227)) # pozadina je plava
    glavni_buffer.blit( brod_zarotirani, (brod_PozicijaX - brod_zarotirani.get_width() / 2, brod_PozicijaY - brod_zarotirani.get_height() / 2) )
    
    # update prozora nakon svih crtanja
    pygame.display.flip()  
    
    #ogranicavanje frameratea na 60fps-a
    tajmer.tick(60)
You do not have the required permissions to view the files attached to this post.
a.k.a. Koki

User avatar
EmP
Veliki brat malih trokuta
Posts: 498
Joined: Tue Sep 01, 2009 10:14 pm
Smallest prime number bigger than 20: 23
Location: Zagreb
Contact:

Re: [CLANAK 6.] - Game mechanics 2: Upotreba vektora i trigonome

Post by EmP » Mon Dec 06, 2010 1:21 am

Oho, članak s matematičkog područja! Sad ću ja bit zločesti recenzent :twisted:

Dvije primjedbe:

1.
Vektor se ne sastoji od smjera i duljine (magnitude). Mislim, to je ok u polarnom koordinatnom sustavu (sustav u kojem su koordinate udaljenost od ishodišta i kut), al cijeli članak ti podrazumjeva Kartezijev koordinatni (obični s x i y koordinatama). Vektor se sastoji od dvije komponente od kojih prva predstavlja promjenu na x osi a druga promjenu na y osi.

2.
Vektor nije "smješten u prostoru". Kolko sam skužio iz teksta, u uvjerenju si da vektori imaju početnu i krajnju točku. Nemaju, vektor je jednostavno razlika koordinata dviju točaka. Ak uzmemo točke A=(1,1), B=(3,1), C=(-100,60) i D=(-98,60), vektori AB i CD predstavljaju apsolutno isti vektor a to je vektor (2,0).

Nama u osnovnoj kad su nam prvu put predavali o vektorima, rekli su nam prvo da su to usmjerene dužine. Ono, kikiću u osnovnoj su dobro upoznati s konceptom dužine i onda se na to samo nadogradila strijelia na jedan kraj dužine. Formalno, ak se ne varam, vektor se definira kao promjena položaja. Zato je vektor prirodan odabir za opis gibanja.

Nadalje, vidim da se odnosiš prema x i y komponentama vektora kao prema odvojenim podacima. Probaj čitatelju ostaviti dojam ko da su oba potpodatak istog objekta, da se natjera čitatelja da zamišlja vektor ko jednu crticu a ne ko dva broja. Vidim da je opet isplivalo usmjeravanje rotiranje jednog objekta prema drugom :). Zato ja rađe simuliram 2D s 3D nego da se patim manama biblioteka za 2D grafiku. Drugi problem tipičnih bilbioteka za 2D si isto spomenuo a to je da y ide prema dolje a ne prema gore. U 3D-u si s matricom projekcije (npr. matricom za ortogonalnu projekciju) moš stavit da ti je donji lijevi kraj (0, 0) a gornji desni (800, 600). Čak ti veličina prozora ne mora bit 800x600 :P Rotacija "prema mišu" izvede se bez trigonometrije matricom transformacije u kojoj je npr. x os vektor smjera od objekta prema mišu a y os okomita na taj smjer.

U članku objašnjavaš banalne stvari kao što je račuanje udaljenosti između dviju točaka. I još k tome daješ link di se to objašnjava, za kakvu publiku pišeš?

Unity vektor mi Hrvati zovemo jedinični vektor. Također za jedinični vektor čiji su smjer i orijentacija (ali ne nužno duljina) jednaki nekom drugom vektoru, kažemo da mu je on (jedinični vektor drugom vektoru) vektor smjera.
Nexus 64213 blog - IKON: moja verzija JSONa
Stareater blog - lijepe slike kak napreduje kod

User avatar
Danijel Korent
Romulanski špijun na Zemlji
Posts: 1583
Joined: Fri Sep 05, 2008 12:35 pm
Smallest prime number bigger than 20: 23
Location: Njemacka
Contact:

Re: [CLANAK 6.] - Game mechanics 2: Upotreba vektora i trigonome

Post by Danijel Korent » Mon Dec 06, 2010 9:15 pm

EmP wrote:Oho, članak s matematičkog područja! Sad ću ja bit zločesti recenzent :twisted:

Dvije primjedbe:
kak bi rekli u nasem dragom saboru, replika na tvoje primjedbe:

Krivo si svatio poantu poglavlja, izgleda da si svatio da ovdje pokusavam nekog nauciti neke matematicke konstrukcije, pojmove i definicije, no ja u ovom clanku pokusavam objasniti kako da se postignu tri stvari koje mnoge pocetnike dosta muci koliko sam primjetio:

- rotiranje nekog objekta i kretanja u smjeru prema kojem je zarotiran
- okretnja igraca prema koordinatama misa
- simuliranje gravitacije izmedu svemirskih tijela

i to objasniti na najbezbolniji moguci nacin i za one bez ikakvog znanja matematike, cim vise "zdravoseljacki" i na nacin da ga najlakse shvate u kontekstu njihove upotrebe u ovim primjerima, tj sta tocno on je u tim primjerima i sta radi. Dal se moja objasnjenja poklapaju sa matematickim pogledom i definicijom vektora mi nije vazno, fokus ovog tutoriala je naucit na koji nacin ove gore probleme rijesiti, matematicke definicije mogu uciti drugdje ako zele.

Mozda sam malo dao krivo ime ovog poglavlja, al mi nije palo na pamet nista sto zvuci ok (napredna/kompleksna kretanja objekta?) pa sam samo stavio upotreba vektora i trigonometije u igrama.

No dobrodoslo je i ovo matematicko objasnjavanje/ispravljanje, da nebi koga kaj zbunjivalo u skoli

Nadalje, vidim da se odnosiš prema x i y komponentama vektora kao prema odvojenim podacima. Probaj čitatelju ostaviti dojam ko da su oba potpodatak istog objekta, da se natjera čitatelja da zamišlja vektor ko jednu crticu a ne ko dva broja.
ne znam ako si pogledao al u primjerima je to (nadam se) dosta jasno prikazano, al opet sam zelio jasno naglasiti da se barata (samo) sa dva broja.
Vidim da je opet isplivalo usmjeravanje rotiranje jednog objekta prema drugom :). Zato ja rađe simuliram 2D s 3D nego da se patim manama biblioteka za 2D grafiku. Drugi problem tipičnih bilbioteka za 2D si isto spomenuo a to je da y ide prema dolje a ne prema gore. U 3D-u si s matricom projekcije (npr. matricom za ortogonalnu projekciju) moš stavit da ti je donji lijevi kraj (0, 0) a gornji desni (800, 600). Čak ti veličina prozora ne mora bit 800x600 :P Rotacija "prema mišu" izvede se bez trigonometrije matricom transformacije u kojoj je npr. x os vektor smjera od objekta prema mišu a y os okomita na taj smjer.
To je opet jedan korak dalje, mislim da je ono ipak intuivnije za pocetnika...
U članku objašnjavaš banalne stvari kao što je račuanje udaljenosti između dviju točaka. I još k tome daješ link di se to objašnjava, za kakvu publiku pišeš?
Za sve, od onih koji ne znaju nista do pa na dalje - tako je zamisljen cijeli taj tutorial. Ovo poglavlje konkretno je namijenjeno onima koji su zapeli kod rotiranja i kretanja igraca.
a.k.a. Koki

User avatar
bytespiller
DB Contest Winner '09
Posts: 2328
Joined: Fri Sep 12, 2008 11:46 am

Re: [CLANAK 6.] - Game mechanics 2: Upotreba vektora i trigonome

Post by bytespiller » Tue Dec 07, 2010 7:31 pm

Svaka čast koki, odlično objašnjeno!

User avatar
Denis
Sajentist on djuti!
Posts: 2618
Joined: Tue Aug 26, 2008 9:35 pm
Smallest prime number bigger than 20: 23

Re: [CLANAK 6.] - Game mechanics 2: Upotreba vektora i trigonome

Post by Denis » Thu Dec 09, 2010 6:58 pm

Tek sad uspio pročitati. Jako lijepo, super objašnjeno! I super je što i nije toliko objašnjene vezano uz Python (taman onoliko koliko treba), pa se vrlo lako primjer može pratiti i u drugim jezicima. :tup:

Post Reply

Who is online

Users browsing this forum: No registered users and 1 guest