Comments and tweaks
parent
9c142044ad
commit
f1b75d82af
|
@ -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
16
main.py
|
@ -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__":
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 57 KiB |
|
@ -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])):
|
||||
|
|
17
src/mesh.py
17
src/mesh.py
|
@ -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),
|
||||
]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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))
|
||||
|
|
Loading…
Reference in New Issue