✨ Attackers
This commit is contained in:
parent
046f33b68e
commit
901c01af2f
@ -2,9 +2,9 @@
|
|||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Cyberattack</title>
|
<title>Cloudflare Simulator</title>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
body, html {
|
body, html {
|
||||||
|
@ -4,6 +4,7 @@ import { vector2d } from "./vector";
|
|||||||
|
|
||||||
import Player from "./objects/player";
|
import Player from "./objects/player";
|
||||||
import Crystal from "./objects/crystal";
|
import Crystal from "./objects/crystal";
|
||||||
|
import Attacker from "./objects/enemies/attacker";
|
||||||
import TimerProvider from "./providers/timer";
|
import TimerProvider from "./providers/timer";
|
||||||
import CooldownProvider from "./providers/cooldown";
|
import CooldownProvider from "./providers/cooldown";
|
||||||
import CooldownUI from "./ui/cooldown";
|
import CooldownUI from "./ui/cooldown";
|
||||||
@ -23,8 +24,17 @@ export default function Stage() {
|
|||||||
let centerPosition = new vector2d(0, 0);
|
let centerPosition = new vector2d(0, 0);
|
||||||
let playerPosition = new vector2d(0, 0);
|
let playerPosition = new vector2d(0, 0);
|
||||||
|
|
||||||
|
let randomPosition = new vector2d(0, 0);
|
||||||
|
|
||||||
let [ready, setReady] = createSignal(false);
|
let [ready, setReady] = createSignal(false);
|
||||||
|
|
||||||
|
function calcRandomPosition(maxX: number, maxY: number) {
|
||||||
|
return new vector2d(
|
||||||
|
Math.floor(Math.random() * maxX),
|
||||||
|
Math.floor(Math.random() * maxY)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function initializeStage(element: HTMLDivElement) {
|
function initializeStage(element: HTMLDivElement) {
|
||||||
centerPosition = new vector2d(
|
centerPosition = new vector2d(
|
||||||
element.clientWidth / 2,
|
element.clientWidth / 2,
|
||||||
@ -32,6 +42,12 @@ export default function Stage() {
|
|||||||
);
|
);
|
||||||
playerPosition = centerPosition.clone();
|
playerPosition = centerPosition.clone();
|
||||||
|
|
||||||
|
randomPosition = calcRandomPosition(
|
||||||
|
element.clientWidth,
|
||||||
|
element.clientHeight
|
||||||
|
);
|
||||||
|
console.log("example pos:", randomPosition);
|
||||||
|
|
||||||
setReady(true);
|
setReady(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -40,12 +56,14 @@ export default function Stage() {
|
|||||||
return (
|
return (
|
||||||
<TimerProvider>
|
<TimerProvider>
|
||||||
<CooldownProvider>
|
<CooldownProvider>
|
||||||
<StageContainer ref={container}>
|
<StageContainer class="stage" ref={container}>
|
||||||
<Show when={ready()}>
|
<Show when={ready()}>
|
||||||
{/* Objects */}
|
{/* Objects */}
|
||||||
<Crystal size={128} position={centerPosition} />
|
<Crystal size={128} position={centerPosition} />
|
||||||
<Player size={64} position={playerPosition} />
|
<Player size={64} position={playerPosition} />
|
||||||
|
|
||||||
|
<Attacker size={32} position={randomPosition} />
|
||||||
|
|
||||||
{/* UI Elements */}
|
{/* UI Elements */}
|
||||||
<CooldownUI />
|
<CooldownUI />
|
||||||
</Show>
|
</Show>
|
||||||
|
115
src/stage/objects/enemies/attacker.tsx
Normal file
115
src/stage/objects/enemies/attacker.tsx
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
import { styled } from "solid-styled-components";
|
||||||
|
import { vector2d } from "../../vector";
|
||||||
|
import { createSignal, onCleanup, onMount } from "solid-js";
|
||||||
|
import { getFacingAngle } from "../raycast";
|
||||||
|
import { useTimer } from "../../providers/timer";
|
||||||
|
|
||||||
|
export default function Attacker(props: {
|
||||||
|
size?: number;
|
||||||
|
speed?: number;
|
||||||
|
position?: vector2d;
|
||||||
|
}) {
|
||||||
|
const width = props.size ? props.size / 6 : 2;
|
||||||
|
const height = props.size ?? 20;
|
||||||
|
|
||||||
|
// Just some random comment
|
||||||
|
// https://youtube.com/ishowspeed
|
||||||
|
//
|
||||||
|
// But I promise
|
||||||
|
// Attackers' speed will slower than player(4u)
|
||||||
|
let speed = props.speed ?? 2;
|
||||||
|
|
||||||
|
// Position & Movement
|
||||||
|
|
||||||
|
const [x, setX] = createSignal(
|
||||||
|
props.position?.x ? props.position?.x - width / 2 : 0
|
||||||
|
);
|
||||||
|
const [y, setY] = createSignal(
|
||||||
|
props.position?.y ? props.position?.y - height / 2 : 0
|
||||||
|
);
|
||||||
|
|
||||||
|
// Those code below are copied from player.tsx
|
||||||
|
// I don't know why I didn't make a base class to impl the movement
|
||||||
|
// We are using the function so that we cannot use the extend keyword
|
||||||
|
// Howdy!
|
||||||
|
let velocity = new vector2d(0, 0);
|
||||||
|
|
||||||
|
function move() {
|
||||||
|
setX((x) => (x += velocity.x));
|
||||||
|
setY((y) => (y += velocity.y));
|
||||||
|
}
|
||||||
|
|
||||||
|
// These two variables should be the "Renderer" part
|
||||||
|
// But we need use them to calculate the movement
|
||||||
|
// So whatever
|
||||||
|
//
|
||||||
|
// It just works —— Mr. Todd
|
||||||
|
let element: any;
|
||||||
|
let target: any;
|
||||||
|
|
||||||
|
function calcTrackingVelocity() {
|
||||||
|
let rectSource = element.getBoundingClientRect();
|
||||||
|
let rectTarget = target.getBoundingClientRect();
|
||||||
|
// If we are higher than target, then move down
|
||||||
|
if (rectSource.top < rectTarget.top) velocity.y = speed;
|
||||||
|
// If we are lower than target, then move up
|
||||||
|
else velocity.y = -speed;
|
||||||
|
// etc...
|
||||||
|
if (rectSource.left < rectTarget.left) velocity.x = speed;
|
||||||
|
// etc...
|
||||||
|
else velocity.x = -speed;
|
||||||
|
// I really don't know what is the relationship between them.
|
||||||
|
// But it just works.
|
||||||
|
//
|
||||||
|
// We use inline if and else to save more space to write those useless comments...
|
||||||
|
}
|
||||||
|
|
||||||
|
// Every tick move a little bit
|
||||||
|
const [{ subscribe }] = useTimer();
|
||||||
|
subscribe(() => {
|
||||||
|
if (element && target) {
|
||||||
|
calcTrackingVelocity();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
const loop = setInterval(() => move(), 10);
|
||||||
|
onCleanup(() => clearInterval(loop));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Renderer
|
||||||
|
|
||||||
|
const [facingAngle, setFacingAngle] = createSignal(0);
|
||||||
|
|
||||||
|
function calcFacing() {
|
||||||
|
target = document.querySelector<HTMLElement>(".crystal");
|
||||||
|
if (element && target) {
|
||||||
|
setFacingAngle(getFacingAngle(element, target) + 90);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
subscribe(() => calcFacing());
|
||||||
|
|
||||||
|
const RequestElement = styled("svg")`
|
||||||
|
width: ${width.toString()}px;
|
||||||
|
height: ${height.toString()}px;
|
||||||
|
|
||||||
|
transform: rotate(${facingAngle}deg);
|
||||||
|
|
||||||
|
transition-property: transform;
|
||||||
|
transition-timing-function: linear;
|
||||||
|
transition-duration: 150ms;
|
||||||
|
|
||||||
|
position: absolute;
|
||||||
|
`;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RequestElement
|
||||||
|
ref={element}
|
||||||
|
class="hostile-request"
|
||||||
|
style={{ left: `${x()}px`, top: `${y()}px` }}
|
||||||
|
>
|
||||||
|
<rect width={width} height={height} fill="#a71c1c" />
|
||||||
|
</RequestElement>
|
||||||
|
);
|
||||||
|
}
|
11
src/stage/objects/raycast.ts
Normal file
11
src/stage/objects/raycast.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
export function getFacingAngle(
|
||||||
|
source: HTMLElement,
|
||||||
|
target: HTMLElement
|
||||||
|
): number {
|
||||||
|
let rectSource = source.getBoundingClientRect();
|
||||||
|
let rectTarget = target.getBoundingClientRect();
|
||||||
|
const deltaX = rectTarget.left - rectSource.left;
|
||||||
|
const deltaY = rectTarget.top - rectSource.top;
|
||||||
|
const angleInDegrees = Math.atan2(deltaY, deltaX) * (180 / Math.PI);
|
||||||
|
return angleInDegrees;
|
||||||
|
}
|
@ -20,7 +20,7 @@ export default function CooldownUI() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<UIContainer>
|
<UIContainer class="cooldown-promote">
|
||||||
<For each={stack()}>
|
<For each={stack()}>
|
||||||
{(item) => (
|
{(item) => (
|
||||||
<div>
|
<div>
|
||||||
|
Loading…
Reference in New Issue
Block a user