Skip to content

Instantly share code, notes, and snippets.

@snuffyDev
Created March 30, 2023 18:17
Show Gist options
  • Select an option

  • Save snuffyDev/42132d0c8d68ea055f28ac142df90b65 to your computer and use it in GitHub Desktop.

Select an option

Save snuffyDev/42132d0c8d68ea055f28ac142df90b65 to your computer and use it in GitHub Desktop.
import type { Position, Position2D } from "$lib/types/position";
export const getDistanceFromPoints = (p1: Position | Position2D, p2: Position | Position2D): number => {
return Math.sqrt((p2.x - p1.x) ** 2 + (p2.z - p1.z) ** 2);
};
export function getRealPositionFromLocalPosition({ x, z }: Position | Omit<Position, 'y'>) {
return {
x: x + -100 * x,
z: z + -100 * z
};
}
<script lang="ts">
import Door from '$lib/components/Door.svelte';
import Guard from '$lib/components/Guard/Guard.svelte';
import Player from '$lib/components/Player.svelte';
import Wall from '$lib/components/Wall.svelte';
import { PlayerState } from '$lib/stores/player';
import type { Position } from '$lib/types/position';
import type { World } from '$lib/utils/map';
import { objectKeys } from '$lib/utils/object';
import { getDistanceFromPoints } from '$lib/utils/position';
import { frameLoop } from '$lib/utils/raf';
import { onMount } from 'svelte';
export let level: World = [];
export let mode: 'editor' | 'generating' | 'play' = 'play';
let world: HTMLElement;
let previousPlayerPosition: Position;
const gameLoop = frameLoop.add(update);
const walls: InstanceType<typeof Wall>[] = Array.from({ length: level.length }).fill(
[]
) as InstanceType<typeof Wall>[];
const models: InstanceType<typeof Door>[] = [];
const collisions: (string | any)[] = [];
const MODEL_MAP = {
Door: Door
} as const;
function normalize(angle: number): number {
const range = 360;
return ((angle % range) + range) % range;
}
function getAngleBetween(pos1: Position, pos2: Position) {
const dx = pos1.x - pos2.x;
const dz = pos2.z - pos1.z;
const angle = normalize((Math.atan2(dz, dx) * -90) / Math.PI);
return angle;
}
function isAngleBetween(mid: number, start: number, end: number): boolean {
const formattedEnd = end - start < 0 ? end - start + 360 : end - start;
const formattedMid = mid - start < 0 ? mid - start + 360 : mid - start;
return formattedMid < formattedEnd;
}
function checkCollision(position: Omit<Position, 'y'>) {
try {
const wall = level[position!.z][position!.x];
return (
wall.model?.component ||
Object.values(wall.surfaces ?? {}).every((t) => typeof t !== 'object' && t !== ' ')
);
} catch (e) {
return false;
}
}
function isVisible(wall: InstanceType<typeof Wall> | Position) {
let p: Position;
if (wall instanceof Wall) {
p = wall.getPosition() as Position;
} else {
p = wall;
}
const angle = getAngleBetween(p as Position, $PlayerState.position);
const playerViewAngle = -$PlayerState.rotation.y + 90;
const playerViewLeft = normalize(playerViewAngle - 800 / 2);
const playerViewRight = normalize(playerViewAngle + 800 / 2);
return isAngleBetween(angle, playerViewLeft, playerViewRight);
}
let start: number;
function processVisibility(
wall: InstanceType<typeof Wall>,
x: number,
z: number,
collisions: any[]
) {
const pos = wall?.getPosition?.();
if (!pos) return;
for (let i = 0; i < wall.sides.length; i++) {
if (wall.sides[i] === null) continue;
const side = wall.boundSides[i];
const sideVisible = isVisible(pos as Position) ? 'visible' : 'hidden';
if (!side) return;
const position = {
x: Number(side.dataset.x),
z: Number(side.dataset.z)
};
const distance = getDistanceFromPoints(
{ x: position.x, z: position.z },
$PlayerState.position
);
if (distance < 50 && checkCollision(pos)) {
collisions.push(position);
// if (side?.style && side?.style?.visibility !== sideVisible) {
// wall.boundSides[i].style.visibility = sideVisible;
// }
}
}
}
function update(ts: number) {
if (!start) start = ts;
const elapsed = ts - start;
if (!previousPlayerPosition) previousPlayerPosition = $PlayerState.position;
const { x, y, z } = $PlayerState.position ?? { x: 0, y: 0, z: 0 };
world.style.transform = `translate3d(${x}px, ${y}px, ${z}px)`;
if (elapsed > 16) {
for (let idx = 0; idx < walls.length; idx++) {
const wall = walls[idx];
if (!wall) continue;
if (!wall.boundSides) continue;
processVisibility(wall, x, z, collisions);
const pos = wall?.getPosition?.();
if (!pos || !pos.x) continue;
const distance = getDistanceFromPoints({ x: pos.x - 50, z: pos.z }, $PlayerState.position);
if (checkCollision(wall?.getLocalPosition?.()) && distance <= 75) {
collisions.push(pos);
}
}
for (const item of models) {
const pos = item.getPosition();
const distance = getDistanceFromPoints({ x: pos.x - 50, z: pos.z }, $PlayerState.position);
if (checkCollision(item?.getLocalPosition?.()) && distance <= 25) {
collisions.push(pos);
}
}
} else {
start = ts;
}
if (collisions.length) {
collisions.length = 0;
PlayerState.setCanMove(false);
}
previousPlayerPosition = $PlayerState.position;
}
let idx = 0;
onMount(() => {
if (mode !== 'generating') {
world = document.getElementById('world')!;
gameLoop.start();
return () => {
gameLoop.stop();
frameLoop.dispose();
};
}
});
</script>
<svelte:window
on:keydown={(e) => {
if (e.ctrlKey && e.key.toLowerCase() === 'w') e.preventDefault();
}}
/>
<div id="scene">
{#if mode !== 'generating'}
<Player>
<div class="world" id="world">
<Guard />
{#each level as group, section}
{#each group as item, offset}
{#if objectKeys(item.surfaces).every((k) => typeof item.surfaces[k] === 'string' && item.surfaces[k] !== ' ')}
<Wall bind:this={walls[idx++]} {item} {section} {offset} />
{:else if item.model?.component}
<svelte:component
this={MODEL_MAP[item.model.component]}
{section}
{offset}
bind:this={models[models.length]}
{item}
/>
{/if}
{/each}
{/each}
<div class="floor" />
</div></Player
>
{/if}
</div>
<style lang="scss">
#scene {
width: 100%;
height: 100%;
perspective: calc(var(--perspective));
inset: 0;
overflow: hidden;
backface-visibility: hidden;
position: absolute;
transform-style: preserve-3d;
// background-color: rgb(50, 50, 50); /* Sky texture */
}
*,
*::before,
*::after {
box-sizing: border-box;
}
.floor {
min-width: 100%;
height: 100%;
background-color: rgb(104, 104, 104);
backface-visibility: visible;
position: fixed;
// inset: 0;
// bottom: 0;
top: 0;
transform: scale3d(64, 3, 64) translateY(-300px) rotateX(90deg);
backface-visibility: hidden;
}
html,
body {
margin: 0;
padding: 0;
backface-visibility: hidden;
width: 100%;
height: 100%;
}
html,
body {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
transform-style: flat !important;
position: absolute !important;
// inset: 0;
}
#scene {
width: 100%;
height: 100%;
perspective: var(--perspective);
overflow: hidden;
/* Sky texture */
background-size: cover;
}
#camera,
#world {
position: absolute;
top: 50% !important;
left: 50% !important;
// transform-origin: center;
// inset: 0;
backface-visibility: hidden;
will-change: transform;
width: 100%;
height: 100%;
transform-style: preserve-3d;
}
.wall,
.floor {
left: 50%;
top: 50%;
backface-visibility: hidden !important;
// background-size: ;
/* For the instruction text */
font-family: sans-serif;
font-size: 3em;
text-align: center;
line-height: 300px;
/* How to treat the textures */
background-size: 100%;
background-repeat: repeat;
}
</style>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment