Comments and tweaks

master
GreenXenith 2020-05-21 12:07:48 -07:00
parent 9c142044ad
commit f1b75d82af
7 changed files with 42 additions and 12 deletions

View File

@ -16,3 +16,8 @@ _Minecraft written in PyGame. Because why not._
* On my machine runs at 8FPS with 4 blocks, and at 3FPS with 64 blocks.
* Drawing could be sped up using Numpy vectorization, but drawing textured triangles with that would be rather difficult to figure out.
* The closer you get to blocks, the slower it will be. You may need to hold down `ESC` longer to free the cursor.
32x32 map I managed to render:
![32x map](screenshot.png)
Thank you to [Javidx9](https://github.com/OneLoneCoder/)'s 3D renderer tutorials. A portion of the matrix code was derived from his.

16
main.py
View File

@ -1,3 +1,8 @@
"""
PyCraft Game
(Mostly a renderer, but whatever)
"""
import math, numpy, pygame, sys, time
import src.input as input
import src.matrix as matrix
@ -8,6 +13,7 @@ from src.camera import Camera
from src.renderer import Renderer
from src.map import Map
# Camera movement and rotation
def update_camera(camera, dtime):
rn = math.radians(90) # Ninety degrees in radians
ro = math.radians(1) # One degree in radians
@ -54,20 +60,23 @@ class Game:
self.screen_w = screen_w
self.screen_h = screen_h
# Cursor handling
self.visible = False
self.esc = False
pygame.mouse.set_visible(self.visible)
pygame.event.set_grab(not self.visible)
pygame.mouse.set_pos((screen_w / 2, screen_h / 2))
# Initialize camera
self.camera = Camera()
self.camera.set_pos(Vec3(0, 0, -32))
self.camera.set_pos(Vec3(0, 0, -32)) # Set far away to help performance
self.renderer = Renderer(screen_w, screen_h)
# Only generate cube mesh once
self.cube = Cube()
# Load images and define grass block
self.media = {
"grass_block_side": pygame.image.load("media/grass_block_side.png"),
"grass": pygame.image.load("media/grass.png"),
@ -86,6 +95,7 @@ class Game:
self.map = Map(8, False) # Change False to True for testing mode
def start(self):
# Game loop
while True:
begin = time.time()
@ -94,6 +104,7 @@ class Game:
pygame.quit()
sys.exit()
# Free/grab cursor
if not self.esc and input.is_down("esc"):
self.visible = not self.visible
@ -115,6 +126,7 @@ class Game:
# Frame updating
self.renderer.clear()
# Render the blocks
m = self.map.map
for x in range(len(m)):
for z in range(len(m[x])):
@ -124,6 +136,7 @@ class Game:
self.renderer.update()
# Crosshair
mid_w = self.screen_w / 2
mid_h = self.screen_h / 2
pygame.draw.line(self.renderer.screen, (255, 255, 255), (mid_w, mid_h + 10), (mid_w, mid_h - 10))
@ -131,6 +144,7 @@ class Game:
pygame.display.flip() # Double flip to show crosshair
# FPS counter
pygame.display.set_caption(str(round(1.0 / (time.time() - begin), 1)) + "FPS")
if __name__ == "__main__":

BIN
screenshot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

View File

@ -11,6 +11,7 @@ class Map:
self.map[0][1][0] = 1
self.map[1][1][0] = 1
else:
# Generate map using basic perlin noise
p = perlin((size, size), (1, 1))
for x in range(len(p)):
for z in range(len(p[x])):

View File

@ -1,5 +1,6 @@
from .vector import Vec3
# UV coordinates (includes w for perspective correction)
class UV:
def __init__(self, u = 0, v = 0, w = 0):
self.u = float(u)
@ -18,7 +19,7 @@ class Triangle:
self.uv = [UV(0, 0, 0), UV(0, 0, 0), UV(0, 0, 0)]
self.normal = Vec3()
self.color = (255, 255, 255)
self.color = (255, 255, 255) # This is only used for non-textured drawing
self.index = textureIndex
class Mesh:
@ -27,6 +28,7 @@ class Mesh:
if file != "":
self.fromFile(file)
# File loader (used for testing)
def fromFile(self, path):
f = open(path, "r")
lines = f.readlines()
@ -48,30 +50,31 @@ class Mesh:
self.tris = tris
# Cube mesh (premade)
def Cube():
mesh = Mesh()
mesh.tris = [
# SOUTH
# South (Z-)
Triangle(Vec3(0.0, 0.0, 0.0), Vec3(0.0, 1.0, 0.0), Vec3(1.0, 1.0, 0.0), UV(0, 1, 1), UV(0, 0, 1), UV(1, 0, 1), 0),
Triangle(Vec3(0.0, 0.0, 0.0), Vec3(1.0, 1.0, 0.0), Vec3(1.0, 0.0, 0.0), UV(0, 1, 1), UV(1, 0, 1), UV(1, 1, 1), 0),
# EAST
# East (X+)
Triangle(Vec3(1.0, 0.0, 0.0), Vec3(1.0, 1.0, 0.0), Vec3(1.0, 1.0, 1.0), UV(0, 1, 1), UV(0, 0, 1), UV(1, 0, 1), 1),
Triangle(Vec3(1.0, 0.0, 0.0), Vec3(1.0, 1.0, 1.0), Vec3(1.0, 0.0, 1.0), UV(0, 1, 1), UV(1, 0, 1), UV(1, 1, 1), 1),
# NORTH
# North (Z+)
Triangle(Vec3(1.0, 0.0, 1.0), Vec3(1.0, 1.0, 1.0), Vec3(0.0, 1.0, 1.0), UV(0, 1, 1), UV(0, 0, 1), UV(1, 0, 1), 2),
Triangle(Vec3(1.0, 0.0, 1.0), Vec3(0.0, 1.0, 1.0), Vec3(0.0, 0.0, 1.0), UV(0, 1, 1), UV(1, 0, 1), UV(1, 1, 1), 2),
# WEST
# West (X-)
Triangle(Vec3(0.0, 0.0, 1.0), Vec3(0.0, 1.0, 1.0), Vec3(0.0, 1.0, 0.0), UV(0, 1, 1), UV(0, 0, 1), UV(1, 0, 1), 3),
Triangle(Vec3(0.0, 0.0, 1.0), Vec3(0.0, 1.0, 0.0), Vec3(0.0, 0.0, 0.0), UV(0, 1, 1), UV(1, 0, 1), UV(1, 1, 1), 3),
# TOP
# Top (Y+)
Triangle(Vec3(0.0, 1.0, 0.0), Vec3(0.0, 1.0, 1.0), Vec3(1.0, 1.0, 1.0), UV(0, 1, 1), UV(0, 0, 1), UV(1, 0, 1), 4),
Triangle(Vec3(0.0, 1.0, 0.0), Vec3(1.0, 1.0, 1.0), Vec3(1.0, 1.0, 0.0), UV(0, 1, 1), UV(1, 0, 1), UV(1, 1, 1), 4),
# BOTTOM
# Bottom (Y-)
Triangle(Vec3(1.0, 0.0, 1.0), Vec3(0.0, 0.0, 1.0), Vec3(0.0, 0.0, 0.0), UV(0, 1, 1), UV(0, 0, 1), UV(1, 0, 1), 5),
Triangle(Vec3(1.0, 0.0, 1.0), Vec3(0.0, 0.0, 0.0), Vec3(1.0, 0.0, 0.0), UV(0, 1, 1), UV(1, 0, 1), UV(1, 1, 1), 5),
]

View File

@ -1,10 +1,11 @@
# Source: https://pvigier.github.io/2018/06/13/perlin-noise-numpy.html
# I don't know how much of this works. Numpy vectorization is black magic to me.
import numpy
def perlin(shape, res):
def f(t):
return 6*t**5 - 15*t**4 + 10*t**3
delta = (res[0] / shape[0], res[1] / shape[1])
d = (shape[0] // res[0], shape[1] // res[1])
grid = numpy.mgrid[0:res[0]:delta[0],0:res[1]:delta[1]].transpose(1, 2, 0) % 1

View File

@ -3,6 +3,7 @@ from . import matrix, vector
from .vector import Vec3
from .mesh import Triangle, UV
# Plane-line intersection point (used for clipping)
def planeLineIntersection(plane_p, normal, lineStart, lineEnd):
normal = normal.normalize()
d = -vector.dot(normal, plane_p)
@ -12,6 +13,7 @@ def planeLineIntersection(plane_p, normal, lineStart, lineEnd):
line = lineEnd - lineStart
return lineStart + (line * t), t
# Clip triangle against arbitrary plane/line and return new triangles
def clipTriangle(planeP, normal, triangle):
normal = normal.normalize()
@ -94,11 +96,14 @@ def clipTriangle(planeP, normal, triangle):
else: # nInsidePoints == 3
return [triangle]
# Adjust an RGB color based on light value
def lightColor(rgb, light):
c = lambda v: max(0, min(int(v * light), 255))
return int("%02x%02x%02x" % (c(rgb[0]), c(rgb[1]), c(rgb[2])), 16)
# Manually draw textured triangle
def drawTexturedTriangle(pixelBuffer, depthBuffer, triangle, texture, light):
# Sort verticies from y+ to y-
verts = [[triangle.verts[0], triangle.uv[0]], [triangle.verts[1], triangle.uv[1]], [triangle.verts[2], triangle.uv[2]]]
s = sorted(verts, key = lambda s: s[0].y)
@ -268,16 +273,16 @@ class Renderer:
self.screen_h = screen_h
self.screen = pygame.display.set_mode((screen_w, screen_h))
self.pixelBuffer = numpy.zeros((screen_w, screen_h))
self.depthBuffer = numpy.zeros((screen_w, screen_h))
self.pixelBuffer = numpy.zeros((screen_w, screen_h)) # Pixel colors
self.depthBuffer = numpy.zeros((screen_w, screen_h)) # Pixel depths
self.clock = pygame.time.Clock()
pygame.mouse.get_rel()
# Generate projection matrix
self.matProj = matrix.perspective(screen_h / screen_w, 90.0, 0.1, 1000.0)
def clear(self):
# self.screen.fill((0, 0, 0))
self.pixelBuffer = numpy.full((self.screen_w, self.screen_h), 0x97C5FE) # Sky color
self.depthBuffer = numpy.zeros((self.screen_w, self.screen_h))
@ -406,6 +411,7 @@ class Renderer:
tLight = max(0.1, vector.dot(light, tri.normal))
drawTexturedTriangle(self.pixelBuffer, self.depthBuffer, tri, textures[tri.index], tLight)
# # Debug stuff
# points = []
# for v in tri.verts:
# points.append((v.x, v.y))