Created
March 30, 2023 18:17
-
-
Save snuffyDev/42132d0c8d68ea055f28ac142df90b65 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 | |
| }; | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <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