✨ Attackers
This commit is contained in:
parent
046f33b68e
commit
901c01af2f
@ -2,9 +2,9 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<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" />
|
||||
<title>Cyberattack</title>
|
||||
<title>Cloudflare Simulator</title>
|
||||
|
||||
<style>
|
||||
body, html {
|
||||
|
@ -4,6 +4,7 @@ import { vector2d } from "./vector";
|
||||
|
||||
import Player from "./objects/player";
|
||||
import Crystal from "./objects/crystal";
|
||||
import Attacker from "./objects/enemies/attacker";
|
||||
import TimerProvider from "./providers/timer";
|
||||
import CooldownProvider from "./providers/cooldown";
|
||||
import CooldownUI from "./ui/cooldown";
|
||||
@ -23,8 +24,17 @@ export default function Stage() {
|
||||
let centerPosition = new vector2d(0, 0);
|
||||
let playerPosition = new vector2d(0, 0);
|
||||
|
||||
let randomPosition = new vector2d(0, 0);
|
||||
|
||||
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) {
|
||||
centerPosition = new vector2d(
|
||||
element.clientWidth / 2,
|
||||
@ -32,6 +42,12 @@ export default function Stage() {
|
||||
);
|
||||
playerPosition = centerPosition.clone();
|
||||
|
||||
randomPosition = calcRandomPosition(
|
||||
element.clientWidth,
|
||||
element.clientHeight
|
||||
);
|
||||
console.log("example pos:", randomPosition);
|
||||
|
||||
setReady(true);
|
||||
}
|
||||
|
||||
@ -40,12 +56,14 @@ export default function Stage() {
|
||||
return (
|
||||
<TimerProvider>
|
||||
<CooldownProvider>
|
||||
<StageContainer ref={container}>
|
||||
<StageContainer class="stage" ref={container}>
|
||||
<Show when={ready()}>
|
||||
{/* Objects */}
|
||||
<Crystal size={128} position={centerPosition} />
|
||||
<Player size={64} position={playerPosition} />
|
||||
|
||||
<Attacker size={32} position={randomPosition} />
|
||||
|
||||
{/* UI Elements */}
|
||||
<CooldownUI />
|
||||
</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 (
|
||||
<UIContainer>
|
||||
<UIContainer class="cooldown-promote">
|
||||
<For each={stack()}>
|
||||
{(item) => (
|
||||
<div>
|
||||
|
Loading…
Reference in New Issue
Block a user