3D世界
讓我們現在開始寫一個3D的程式,鞏固一下這幾次學習的東西。因為我們還沒有好好深入如何畫3D物體,暫時就先用最簡單的投影(上次討論過的第二種)方法來畫吧。這個程式畫一個空間裡的立方體,只不過各個部分並不會隨著距離而產生大小上的變化。
您可以看到,很多的小球構成了立方體的各個邊,通過按住方向鍵,可以水平或垂直方向的更改“攝像頭”的位置,Q和A鍵會把攝像頭拉近或拉遠,而W和S會改變視距,綠色的三角是視距和視角的示意圖。fov角大的話,立方體就顯得比較短,反之就顯得比較長。
程式碼稍微有點長,下面有解釋,靜下心來慢慢閱讀。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 |
import pygame from pygame.locals import * from gameobjects.vector3 import Vector3 from math import * from random import randint SCREEN_SIZE = (640, 480) CUBE_SIZE = 300 def calculate_viewing_distance(fov, screen_width): d = (screen_width/2.0) / tan(fov/2.0) return d def run(): pygame.init() screen = pygame.display.set_mode(SCREEN_SIZE, 0) default_font = pygame.font.get_default_font() font = pygame.font.SysFont(default_font, 24) ball = pygame.image.load("ball.png").convert_alpha() # 3D points points = [] fov = 90. # Field of view viewing_distance = calculate_viewing_distance(radians(fov), SCREEN_SIZE[0]) # 邊沿的一系列點 for x in xrange(0, CUBE_SIZE+1, 20): edge_x = x == 0 or x == CUBE_SIZE for y in xrange(0, CUBE_SIZE+1, 20): edge_y = y == 0 or y == CUBE_SIZE for z in xrange(0, CUBE_SIZE+1, 20): edge_z = z == 0 or z == CUBE_SIZE if sum((edge_x, edge_y, edge_z)) >= 2: point_x = float(x) - CUBE_SIZE/2 point_y = float(y) - CUBE_SIZE/2 point_z = float(z) - CUBE_SIZE/2 points.append(Vector3(point_x, point_y, point_z)) # 以z序儲存,類似於css中的z-index def point_z(point): return point.z points.sort(key=point_z, reverse=True) center_x, center_y = SCREEN_SIZE center_x /= 2 center_y /= 2 ball_w, ball_h = ball.get_size() ball_center_x = ball_w / 2 ball_center_y = ball_h / 2 camera_position = Vector3(0.0, 0.0, -700.) camera_speed = Vector3(300.0, 300.0, 300.0) clock = pygame.time.Clock() while True: for event in pygame.event.get(): if event.type == QUIT: return screen.fill((0, 0, 0)) pressed_keys = pygame.key.get_pressed() time_passed = clock.tick() time_passed_seconds = time_passed / 1000. direction = Vector3() if pressed_keys[K_LEFT]: direction.x = -1.0 elif pressed_keys[K_RIGHT]: direction.x = +1.0 if pressed_keys[K_UP]: direction.y = +1.0 elif pressed_keys[K_DOWN]: direction.y = -1.0 if pressed_keys[K_q]: direction.z = +1.0 elif pressed_keys[K_a]: direction.z = -1.0 if pressed_keys[K_w]: fov = min(179., fov+1.) w = SCREEN_SIZE[0] viewing_distance = calculate_viewing_distance(radians(fov), w) elif pressed_keys[K_s]: fov = max(1., fov-1.) w = SCREEN_SIZE[0] viewing_distance = calculate_viewing_distance(radians(fov), w) camera_position += direction * camera_speed * time_passed_seconds # 繪製點 for point in points: x, y, z = point - camera_position if z > 0: x = x * viewing_distance / z y = -y * viewing_distance / z x += center_x y += center_y screen.blit(ball, (x-ball_center_x, y-ball_center_y)) # 繪製表 diagram_width = SCREEN_SIZE[0] / 4 col = (50, 255, 50) diagram_points = [] diagram_points.append( (diagram_width/2, 100+viewing_distance/4) ) diagram_points.append( (0, 100) ) diagram_points.append( (diagram_width, 100) ) diagram_points.append( (diagram_width/2, 100+viewing_distance/4) ) diagram_points.append( (diagram_width/2, 100) ) pygame.draw.lines(screen, col, False, diagram_points, 2) # 繪製文字 white = (255, 255, 255) cam_text = font.render("camera = "+str(camera_position), True, white) screen.blit(cam_text, (5, 5)) fov_text = font.render("field of view = %i"%int(fov), True, white) screen.blit(fov_text, (5, 35)) txt = "viewing distance = %.3f"%viewing_distance d_text = font.render(txt, True, white) screen.blit(d_text, (5, 65)) pygame.display.update() if __name__ == "__main__": run() |
上面的例子使用Vector3來管理向量資料,點的儲存是按照z座標來的,這樣在blit的時候,離攝像機近的後畫,就可以覆蓋遠的,否則看起來就太奇怪了……
在主迴圈的程式碼中,會根據按鍵攝像頭會更改位置——當然這是使用者的感覺,實際上程式碼做的是更改了點的位置。而3D的移動和2D是非常像的,只不過多了一個z來判斷覆蓋遠近(也就是繪製順序),一樣的基於時間移動,一樣的向量運算,只是由Vector2變為了Vector3。
然後程式碼需要繪製全部的點。首先,點的位置需要根據camera_position變數校正,如果結果的z比0還大,說明點在攝像頭之前,需要畫的,否則就是不需要畫。y需要校準一下方向,最後把x、y定位在中間(小球還是有一點點尺寸的)。
留下的程式碼是給出資訊的,都是我們學習過的東西了。
如果想好好學習,把立方體換成其他的影象吧!改一下更能加深印象。
3D第一部分總結
3D是迄今為止遊戲發展中最大的里程碑(下一個會是什麼呢?虛擬體驗?),我們這幾次學習的,是3D的基礎,你可以看到,僅有2D繪圖經驗也能很方便的過渡過來。僅僅是Vector2→Vector3,擔任3D向量還是有一些特有的操作的,需要慢慢學習,但是基本的思想不會變。
但是,請繼續思考~ 難道所有的3D遊戲就是一系列的3D座標再手動轉換為2D畫上去就好了?很可惜或者說很幸運不是的,我們有3D引擎來做這些事情,對Pygame來說,原生的3D處理時不存在的,那麼如何真正繪製3D畫面?有一個非常廣泛的選擇——OpenGL,不瞭解的請自行Wiki,不過OpenGL並不是Pygame的一部分,而且3D實際做下去實在很繁雜,這個系列就暫時不深入講解了。
儘管有3D引擎幫我們做投影等事情,我們這幾次學的東西絕對不是白費,對基礎的一定了解可以更好的寫入3D遊戲,對畫面的掌握也會更好。如果有需要,這個系列的主線完成後,會根據大家的要求追加講解OpenGL的內容。
接下來開始講解Pygame中的聲音,基本遊戲製作的全部知識我們都快學習完了:)