implement block break particles, version 1.0.2

This commit is contained in:
LabyStudio
2022-05-15 21:05:11 +02:00
parent 789ecdd2b4
commit 1e10dda0e2
11 changed files with 386 additions and 114 deletions
+1
View File
@@ -31,6 +31,7 @@ Click [here](https://labystudio.de/page/minecraft/) for a demo!
- Arm swing animation
- Walking animation
- Crouch animation
- Block break particles
- World
- 16x16x16 Chunks
- Block type, data, sky & block lightning
+10 -1
View File
@@ -15,10 +15,11 @@ import GrassColorizer from "./render/GrassColorizer.js";
import GuiMainMenu from "./gui/screens/GuiMainMenu.js";
import GuiLoadingScreen from "./gui/screens/GuiLoadingScreen.js";
import * as THREE from "../../../../../libraries/three.module.js";
import ParticleRenderer from "./render/particle/ParticleRenderer.js";
export default class Minecraft {
static VERSION = "1.0.1"
static VERSION = "1.0.2"
static URL_GITHUB = "https://github.com/labystudio/js-minecraft";
/**
@@ -65,6 +66,8 @@ export default class Minecraft {
// Grass colorizer
this.grassColorizer = new GrassColorizer(this);
this.particleRenderer = new ParticleRenderer(this);
// Update window size
this.window.updateWindowSize();
@@ -219,6 +222,9 @@ export default class Minecraft {
// Tick the player
this.player.onUpdate();
// Tick particle renderer
this.particleRenderer.onTick();
}
// Tick the screen
@@ -293,6 +299,9 @@ export default class Minecraft {
1.0
);
// Spawn particle
this.particleRenderer.spawnBlockBreakParticle(this.world, hitResult.x, hitResult.y, hitResult.z);
// Destroy block
this.world.setBlockAt(hitResult.x, hitResult.y, hitResult.z, 0);
}
@@ -1,11 +1,13 @@
import BoundingBox from "../../util/BoundingBox.js";
import MathHelper from "../../util/MathHelper.js";
import Random from "../../util/Random.js";
export default class Entity {
constructor(minecraft, world) {
this.minecraft = minecraft;
this.world = world;
this.random = new Random();
this.renderer = this.minecraft.worldRenderer.entityRenderManager.createEntityRendererByEntity(this);
@@ -13,10 +15,15 @@ export default class Entity {
this.y = 0;
this.z = 0;
this.width = 0.0;
this.height = 0.0;
this.motionX = 0;
this.motionY = 0;
this.motionZ = 0;
this.stepHeight = 0.0;
this.onGround = false;
this.sneaking = false;
@@ -35,8 +42,36 @@ export default class Entity {
this.nextStepDistance = 1;
this.ticksExisted = 0;
this.isDead = false;
this.boundingBox = new BoundingBox();
this.setPosition(this.x, this.y, this.z);
}
setPosition(x, y, z) {
// Update position
this.x = x;
this.y = y;
this.z = z;
// Update bounding box
let width = this.width / 2;
this.boundingBox = new BoundingBox(
x - width,
y,
z - width,
x + width,
y + this.height,
z + width
);
this.motionX = 0;
this.motionY = 0;
this.motionZ = 0;
this.prevX = this.x;
this.prevY = this.y;
this.prevZ = this.z;
}
onUpdate() {
@@ -67,4 +102,97 @@ export default class Entity {
return this.boundingBox.height() * 0.8;
}
moveCollide(targetX, targetY, targetZ) {
// Target position
let originalTargetX = targetX;
let originalTargetY = targetY;
let originalTargetZ = targetZ;
if (this.onGround && this.sneaking) {
for (; targetX !== 0.0 && this.world.getCollisionBoxes(this.boundingBox.offset(targetX, -this.stepHeight, 0.0)).length === 0; originalTargetX = targetX) {
if (targetX < 0.05 && targetX >= -0.05) {
targetX = 0.0;
} else if (targetX > 0.0) {
targetX -= 0.05;
} else {
targetX += 0.05;
}
}
for (; targetZ !== 0.0 && this.world.getCollisionBoxes(this.boundingBox.offset(0.0, -this.stepHeight, targetZ)).length === 0; originalTargetZ = targetZ) {
if (targetZ < 0.05 && targetZ >= -0.05) {
targetZ = 0.0;
} else if (targetZ > 0.0) {
targetZ -= 0.05;
} else {
targetZ += 0.05;
}
}
for (; targetX !== 0.0 && targetZ !== 0.0 && this.world.getCollisionBoxes(this.boundingBox.offset(targetX, -this.stepHeight, targetZ)).length === 0; originalTargetZ = targetZ) {
if (targetX < 0.05 && targetX >= -0.05) {
targetX = 0.0;
} else if (targetX > 0.0) {
targetX -= 0.05;
} else {
targetX += 0.05;
}
originalTargetX = targetX;
if (targetZ < 0.05 && targetZ >= -0.05) {
targetZ = 0.0;
} else if (targetZ > 0.0) {
targetZ -= 0.05;
} else {
targetZ += 0.05;
}
}
}
// Get level tiles as bounding boxes
let boundingBoxList = this.world.getCollisionBoxes(this.boundingBox.expand(targetX, targetY, targetZ));
// Move bounding box
for (let aABB in boundingBoxList) {
targetY = boundingBoxList[aABB].clipYCollide(this.boundingBox, targetY);
}
this.boundingBox.move(0.0, targetY, 0.0);
for (let aABB in boundingBoxList) {
targetX = boundingBoxList[aABB].clipXCollide(this.boundingBox, targetX);
}
this.boundingBox.move(targetX, 0.0, 0.0);
for (let aABB in boundingBoxList) {
targetZ = boundingBoxList[aABB].clipZCollide(this.boundingBox, targetZ);
}
this.boundingBox.move(0.0, 0.0, targetZ);
this.onGround = originalTargetY !== targetY && originalTargetY < 0.0;
// Stop motion on collision
if (originalTargetX !== targetX) {
this.motionX = 0.0;
}
if (originalTargetY !== targetY) {
this.motionY = 0.0;
}
if (originalTargetZ !== targetZ) {
this.motionZ = 0.0;
}
// Update position
this.x = (this.boundingBox.minX + this.boundingBox.maxX) / 2.0;
this.y = this.boundingBox.minY;
this.z = (this.boundingBox.minZ + this.boundingBox.maxZ) / 2.0;
// Horizontal collision?
return originalTargetX !== targetX || originalTargetZ !== targetZ;
}
kill() {
this.isDead = true;
}
}
@@ -1,6 +1,5 @@
import Inventory from "../inventory/Inventory.js";
import EntityLiving from "./EntityLiving.js";
import BoundingBox from "../../util/BoundingBox.js";
import Block from "../world/block/Block.js";
import MathHelper from "../../util/MathHelper.js";
import Keyboard from "../../util/Keyboard.js";
@@ -44,6 +43,9 @@ export default class PlayerEntity extends EntityLiving {
this.cameraPitch = 0;
this.prevCameraYaw = 0;
this.prevCameraPitch = 0;
this.width = 0.6;
this.height = 1.8;
}
respawn() {
@@ -51,28 +53,6 @@ export default class PlayerEntity extends EntityLiving {
this.setPosition(spawn.x, spawn.y, spawn.z);
}
setPosition(x, y, z) {
let width = 0.3;
let height = 0.9;
this.boundingBox = new BoundingBox(
x - width,
y - height,
z - width,
x + width,
y + height,
z + width
);
this.motionX = 0;
this.motionY = 0;
this.motionZ = 0;
// Update position
this.x = (this.boundingBox.minX + this.boundingBox.maxX) / 2.0;
this.y = this.boundingBox.minY;
this.z = (this.boundingBox.minZ + this.boundingBox.maxZ) / 2.0;
}
turn(motionX, motionY) {
let sensitivity = this.minecraft.settings.sensitivity / 500;
this.rotationYaw = this.rotationYaw + motionX * sensitivity;
@@ -366,95 +346,6 @@ export default class PlayerEntity extends EntityLiving {
this.sneaking = sneaking;
}
moveCollide(targetX, targetY, targetZ) {
// Target position
let originalTargetX = targetX;
let originalTargetY = targetY;
let originalTargetZ = targetZ;
if (this.onGround && this.sneaking) {
for (; targetX !== 0.0 && this.world.getCollisionBoxes(this.boundingBox.offset(targetX, -this.stepHeight, 0.0)).length === 0; originalTargetX = targetX) {
if (targetX < 0.05 && targetX >= -0.05) {
targetX = 0.0;
} else if (targetX > 0.0) {
targetX -= 0.05;
} else {
targetX += 0.05;
}
}
for (; targetZ !== 0.0 && this.world.getCollisionBoxes(this.boundingBox.offset(0.0, -this.stepHeight, targetZ)).length === 0; originalTargetZ = targetZ) {
if (targetZ < 0.05 && targetZ >= -0.05) {
targetZ = 0.0;
} else if (targetZ > 0.0) {
targetZ -= 0.05;
} else {
targetZ += 0.05;
}
}
for (; targetX !== 0.0 && targetZ !== 0.0 && this.world.getCollisionBoxes(this.boundingBox.offset(targetX, -this.stepHeight, targetZ)).length === 0; originalTargetZ = targetZ) {
if (targetX < 0.05 && targetX >= -0.05) {
targetX = 0.0;
} else if (targetX > 0.0) {
targetX -= 0.05;
} else {
targetX += 0.05;
}
originalTargetX = targetX;
if (targetZ < 0.05 && targetZ >= -0.05) {
targetZ = 0.0;
} else if (targetZ > 0.0) {
targetZ -= 0.05;
} else {
targetZ += 0.05;
}
}
}
// Get level tiles as bounding boxes
let boundingBoxList = this.world.getCollisionBoxes(this.boundingBox.expand(targetX, targetY, targetZ));
// Move bounding box
for (let aABB in boundingBoxList) {
targetY = boundingBoxList[aABB].clipYCollide(this.boundingBox, targetY);
}
this.boundingBox.move(0.0, targetY, 0.0);
for (let aABB in boundingBoxList) {
targetX = boundingBoxList[aABB].clipXCollide(this.boundingBox, targetX);
}
this.boundingBox.move(targetX, 0.0, 0.0);
for (let aABB in boundingBoxList) {
targetZ = boundingBoxList[aABB].clipZCollide(this.boundingBox, targetZ);
}
this.boundingBox.move(0.0, 0.0, targetZ);
this.onGround = originalTargetY !== targetY && originalTargetY < 0.0;
// Stop motion on collision
if (originalTargetX !== targetX) {
this.motionX = 0.0;
}
if (originalTargetY !== targetY) {
this.motionY = 0.0;
}
if (originalTargetZ !== targetZ) {
this.motionZ = 0.0;
}
// Update position
this.x = (this.boundingBox.minX + this.boundingBox.maxX) / 2.0;
this.y = this.boundingBox.minY;
this.z = (this.boundingBox.minZ + this.boundingBox.maxZ) / 2.0;
// Horizontal collision?
return originalTargetX !== targetX || originalTargetZ !== targetZ;
}
getEyeHeight() {
return this.sneaking ? 1.50 : 1.62;
}
@@ -117,6 +117,9 @@ export default class WorldRenderer {
// Render target block
this.renderBlockHitBox(player, partialTicks);
// Render particles
this.minecraft.particleRenderer.renderParticles(player, partialTicks);
// Hide all entities and make them visible during rendering
for (let entity of this.minecraft.world.entities) {
entity.renderer.group.visible = false;
@@ -615,7 +618,7 @@ export default class WorldRenderer {
});
// Flush by rebuilding 8 chunk sections
if(this.flushRebuild) {
if (this.flushRebuild) {
this.flushRebuild = false;
for (let i = 0; i < 8; i++) {
@@ -15,6 +15,9 @@ export default class EntityRenderManager {
}
createEntityRendererByEntity(entity) {
if (!(entity.constructor.name in this.renderers)) {
return null;
}
return new this.renderers[entity.constructor.name].prototype.constructor(this.worldRenderer);
}
}
@@ -0,0 +1,123 @@
import Entity from "../../entity/Entity.js";
import * as THREE from "../../../../../../../libraries/three.module.js";
import Tessellator from "../Tessellator.js";
export default class Particle extends Entity {
constructor(minecraft, world, x, y, z, motionX, motionY, motionZ) {
super(minecraft, world);
this.setPosition(x, y, z);
this.textureIndex = 0;
this.randomX = this.random.nextFloat() * 3;
this.randomY = this.random.nextFloat() * 3;
this.randomZ = (this.random.nextFloat() * 0.5 + 0.5) * 2.0;
this.motionX = motionX + (Math.random() * 2 - 1.0) * 0.4;
this.motionY = motionY + (Math.random() * 2 - 1.0) * 0.4;
this.motionZ = motionZ + (Math.random() * 2 - 1.0) * 0.4;
let strength = (Math.random() + Math.random() + 1.0) * 0.15;
let length = Math.sqrt(this.motionX * this.motionX + this.motionY * this.motionY + this.motionZ * this.motionZ);
this.motionX = (this.motionX / length) * strength * 0.4;
this.motionY = (this.motionY / length) * strength * 0.4 + 0.1;
this.motionZ = (this.motionZ / length) * strength * 0.4;
this.maxTicksExisted = Math.floor(4 / (this.random.nextFloat() * 0.9 + 0.1))
this.color = -1;
this.group = null;
}
onUpdate() {
super.onUpdate();
if (this.ticksExisted >= this.maxTicksExisted) {
this.kill();
}
this.motionY -= 0.04;
this.moveCollide(this.motionX, this.motionY, this.motionZ);
this.motionX *= 0.98;
this.motionY *= 0.98;
this.motionZ *= 0.98;
if (this.onGround) {
this.motionX *= 0.7;
this.motionZ *= 0.7;
}
}
render(rotationX, rotationY, rotationZ, partialTicks) {
let x = ((this.prevX + (this.x - this.prevX) * partialTicks));
let y = ((this.prevY + (this.y - this.prevY) * partialTicks));
let z = ((this.prevZ + (this.z - this.prevZ) * partialTicks));
// Create mesh
if (this.group === null) {
this.rebuild();
}
let factor = 0.1 * this.randomZ;
this.group.scale.x = factor;
this.group.scale.y = factor;
this.group.scale.z = factor;
this.group.rotation.x = rotationX;
this.group.rotation.y = rotationY;
this.group.rotation.z = rotationZ;
this.group.position.x = x;
this.group.position.y = y;
this.group.position.z = z;
this.group.updateMatrix();
}
rebuild() {
this.group = new THREE.Object3D();
this.group.rotation.order = 'ZYX';
let tessellator = new Tessellator();
tessellator.bindTexture(this.minecraft.worldRenderer.textureTerrain);
let minU = ((this.textureIndex % 16) + this.randomX / 4) / 16.0;
let maxU = minU + (16 / 256 / 4);
let minV = (Math.floor(this.textureIndex / 16) + this.randomX / 4) / 16.0;
let maxV = minV + (16 / 256 / 4);
// Flip V
minV = 1 - minV;
maxV = 1 - maxV;
let red = (this.color >> 16 & 255) / 255.0;
let green = (this.color >> 8 & 255) / 255.0;
let blue = (this.color & 255) / 255.0;
let brightness = this.getEntityBrightness();
// Render particle
tessellator.startDrawing();
tessellator.setColor(red * brightness, green * brightness, blue * brightness);
tessellator.addVertexWithUV(0, 1, 0, minU, minV);
tessellator.addVertexWithUV(0, 0, 0, minU, maxV);
tessellator.addVertexWithUV(1, 0, 0, maxU, maxV);
tessellator.addVertexWithUV(1, 1, 0, maxU, minV);
let mesh = tessellator.draw(this.group);
mesh.geometry.center();
this.minecraft.worldRenderer.scene.add(this.group);
}
kill() {
super.kill();
if (this.group !== null) {
this.minecraft.worldRenderer.scene.remove(this.group);
}
}
}
@@ -0,0 +1,79 @@
import MathHelper from "../../../util/MathHelper.js";
import Block from "../../world/block/Block.js";
import ParticleDigging from "./particle/ParticleDigging.js";
export default class ParticleRenderer {
constructor(minecraft) {
this.minecraft = minecraft;
this.particles = [];
}
spawnParticle(particle) {
this.particles.push(particle);
}
onTick() {
for (let i = 0; i < this.particles.length; i++) {
const particle = this.particles[i];
particle.onUpdate();
if (particle.isDead) {
this.particles.splice(i, 1);
i--;
}
}
}
renderParticles(cameraEntity, partialTicks) {
let yaw = cameraEntity.prevRotationYaw + (cameraEntity.rotationYaw - cameraEntity.prevRotationYaw) * partialTicks;
let pitch = cameraEntity.prevRotationPitch + (cameraEntity.rotationPitch - cameraEntity.prevRotationPitch) * partialTicks;
let rotationX = MathHelper.toRadians(pitch);
let rotationY = -MathHelper.toRadians(yaw);
for (let i = 0; i < this.particles.length; i++) {
const particle = this.particles[i];
particle.render(rotationX, rotationY, 0, partialTicks);
}
}
spawnBlockBreakParticle(world, x, y, z) {
let typeId = world.getBlockAt(x, y, z);
if (typeId === 0) {
return;
}
let block = Block.getById(typeId);
let range = 4;
for (let offsetX = 0; offsetX < range; offsetX++) {
for (let offsetY = 0; offsetY < range; offsetY++) {
for (let offsetZ = 0; offsetZ < range; offsetZ++) {
let targetX = x + (offsetX + 0.5) / range;
let targetY = y + (offsetY + 0.5) / range;
let targetZ = z + (offsetZ + 0.5) / range;
let motionX = targetX - x - 0.5;
let motionY = targetY - y - 0.5;
let motionZ = targetZ - z - 0.5;
this.spawnParticle(new ParticleDigging(
this.minecraft,
world,
targetX,
targetY,
targetZ,
motionX,
motionY,
motionZ,
block
));
}
}
}
}
}
@@ -0,0 +1,23 @@
import Particle from "../Particle.js";
export default class ParticleDigging extends Particle {
constructor(minecraft, world, x, y, z, motionX, motionY, motionZ, block) {
super(minecraft, world, x, y, z, motionX, motionY, motionZ);
// Get color multiplier
let color = block.getParticleColor(world, x, y, z);
let red = color >> 16 & 0xFF;
let green = color >> 8 & 0xFF;
let blue = color & 0xFF;
red *= 0.6;
green *= 0.6;
blue *= 0.6;
this.color = red << 16 | green << 8 | blue;
this.textureIndex = block.getTextureForFace(block.getParticleTextureFace());
}
}
@@ -31,6 +31,10 @@ export default class Block {
return BlockRenderType.BLOCK;
}
getParticleTextureFace() {
return EnumBlockFace.TOP;
}
getTextureForFace(face) {
return this.textureSlotId;
}
@@ -52,6 +56,10 @@ export default class Block {
return 0xffffff;
}
getParticleColor(world, x, y, z) {
return this.getColor(world, x, y, z, this.getParticleTextureFace());
}
getLightValue() {
return 0;
}
@@ -26,6 +26,10 @@ export default class BlockGrass extends Block {
return world.minecraft.grassColorizer.getColor(temperature, humidity);
}
getParticleTextureFace() {
return EnumBlockFace.NORTH;
}
getTextureForFace(face) {
switch (face) {
case EnumBlockFace.TOP: