feat: Add new game screen
- Add first person view screen - Add HippieCrosshair class - Controls for crosshair options - Add styles to game module
This commit is contained in:
parent
e7aaf257e3
commit
ebed5a2d42
3 changed files with 328 additions and 0 deletions
230
source/code/game.js
Normal file
230
source/code/game.js
Normal file
|
|
@ -0,0 +1,230 @@
|
||||||
|
class HippieCrosshair {
|
||||||
|
constructor(canvas, options = {}) {
|
||||||
|
this.canvas = canvas;
|
||||||
|
this.ctx = canvas.getContext('2d');
|
||||||
|
|
||||||
|
this.debug = options.debug || false;
|
||||||
|
|
||||||
|
// Crosshair options
|
||||||
|
this.size = options.size || 16;
|
||||||
|
this.thickness = options.thickness || 2;
|
||||||
|
this.color = options.color || '#000';
|
||||||
|
this.gapSize = options.gapSize || 8;
|
||||||
|
this.style = options.style || 'cross';
|
||||||
|
|
||||||
|
// Line options
|
||||||
|
this.lineColor = options.lineColor || '#fff';
|
||||||
|
this.lineWidth = options.lineWidth || 1;
|
||||||
|
this.showConnector = options.showConnector !== false;
|
||||||
|
|
||||||
|
// Symbol options
|
||||||
|
this.symbolDistance = options.symbolDistance || 128; // Distance to draw next symbol
|
||||||
|
this.symbolSpacing = options.symbolSpacing || 64; // Space between symbols
|
||||||
|
this.symbolSize = options.symbolSize || 8;
|
||||||
|
this.symbolColor = options.symbolColor || '#000';
|
||||||
|
this.symbolStyle = options.symbolStyle || 'arrow';
|
||||||
|
|
||||||
|
this.mouseX = canvas.width / 2;
|
||||||
|
this.mouseY = canvas.height / 2;
|
||||||
|
|
||||||
|
this.setupEventListeners();
|
||||||
|
this.animate();
|
||||||
|
}
|
||||||
|
|
||||||
|
setupEventListeners() {
|
||||||
|
document.addEventListener('mousemove', (e) => {
|
||||||
|
this.mouseX = e.clientX;
|
||||||
|
this.mouseY = e.clientY;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
animate() {
|
||||||
|
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
||||||
|
|
||||||
|
if (this.showConnector) {
|
||||||
|
if (this.debug) this.drawLine();
|
||||||
|
this.drawConnector();
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Autom. Zug zum Zentrum hin ermöglichen
|
||||||
|
this.drawCrosshair();
|
||||||
|
|
||||||
|
requestAnimationFrame(() => this.animate());
|
||||||
|
}
|
||||||
|
|
||||||
|
drawLine() {
|
||||||
|
const centerX = this.canvas.width / 2;
|
||||||
|
const centerY = this.canvas.height / 2;
|
||||||
|
|
||||||
|
this.ctx.strokeStyle = this.lineColor;
|
||||||
|
this.ctx.lineWidth = this.lineWidth;
|
||||||
|
this.ctx.beginPath();
|
||||||
|
this.ctx.moveTo(centerX, centerY);
|
||||||
|
this.ctx.lineTo(this.mouseX, this.mouseY);
|
||||||
|
this.ctx.stroke();
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Ausblenden nach Distanz
|
||||||
|
drawConnector() {
|
||||||
|
const centerX = this.canvas.width / 2;
|
||||||
|
const centerY = this.canvas.height / 2;
|
||||||
|
|
||||||
|
const dx = this.mouseX - centerX;
|
||||||
|
const dy = this.mouseY - centerY;
|
||||||
|
const distance = Math.sqrt(dx * dx + dy * dy);
|
||||||
|
|
||||||
|
// Only draw symbols if cursor is far enough from center
|
||||||
|
if (distance < this.symbolDistance) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const angle = Math.atan2(dy, dx);
|
||||||
|
|
||||||
|
// Calculate how many symbols to draw
|
||||||
|
const symbolCount = Math.floor((distance - this.symbolDistance) / this.symbolSpacing);
|
||||||
|
|
||||||
|
for (let i = 0; i < symbolCount; i++) {
|
||||||
|
const symbolDistance = this.symbolDistance + (i * this.symbolSpacing);
|
||||||
|
const symbolX = centerX + Math.cos(angle) * symbolDistance;
|
||||||
|
const symbolY = centerY + Math.sin(angle) * symbolDistance;
|
||||||
|
|
||||||
|
this.drawSymbol(symbolX, symbolY, angle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
drawSymbol(x, y, angle = 0) {
|
||||||
|
this.ctx.fillStyle = this.symbolColor;
|
||||||
|
this.ctx.strokeStyle = this.symbolColor;
|
||||||
|
this.ctx.lineWidth = 1;
|
||||||
|
|
||||||
|
switch (this.symbolStyle) {
|
||||||
|
case 'circle':
|
||||||
|
this.ctx.beginPath();
|
||||||
|
this.ctx.arc(x, y, this.symbolSize / 2, 0, Math.PI * 2);
|
||||||
|
this.ctx.fill();
|
||||||
|
break;
|
||||||
|
case 'diamond':
|
||||||
|
const size = this.symbolSize - (this.symbolSize / 4);
|
||||||
|
this.ctx.beginPath();
|
||||||
|
this.ctx.moveTo(x, y - size);
|
||||||
|
this.ctx.lineTo(x + size, y);
|
||||||
|
this.ctx.lineTo(x, y + size);
|
||||||
|
this.ctx.lineTo(x - size, y);
|
||||||
|
this.ctx.closePath();
|
||||||
|
this.ctx.fill();
|
||||||
|
break;
|
||||||
|
case 'square':
|
||||||
|
this.ctx.fillRect(
|
||||||
|
x - this.symbolSize / 2,
|
||||||
|
y - this.symbolSize / 2,
|
||||||
|
this.symbolSize,
|
||||||
|
this.symbolSize
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case 'arrow':
|
||||||
|
this.arrow(x, y, angle);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
arrow(x, y, angle) {
|
||||||
|
const size = this.symbolSize - (this.symbolSize / 4);
|
||||||
|
|
||||||
|
this.ctx.save();
|
||||||
|
this.ctx.translate(x, y);
|
||||||
|
this.ctx.rotate(angle);
|
||||||
|
|
||||||
|
// Arrow pointing right
|
||||||
|
this.ctx.beginPath();
|
||||||
|
this.ctx.moveTo(size, 0); // Tip
|
||||||
|
this.ctx.lineTo(-size, -size); // Back left
|
||||||
|
// this.ctx.lineTo(-size * 0.4, 0); // Middle
|
||||||
|
this.ctx.lineTo(-size, size); // Back right
|
||||||
|
this.ctx.closePath();
|
||||||
|
this.ctx.fill();
|
||||||
|
|
||||||
|
this.ctx.restore();
|
||||||
|
}
|
||||||
|
|
||||||
|
drawCrosshair() {
|
||||||
|
this.ctx.strokeStyle = this.color;
|
||||||
|
this.ctx.lineWidth = this.thickness;
|
||||||
|
this.ctx.lineCap = 'round';
|
||||||
|
|
||||||
|
switch (this.style) {
|
||||||
|
case 'cross':
|
||||||
|
this.cross();
|
||||||
|
break;
|
||||||
|
case 'circle':
|
||||||
|
this.circle();
|
||||||
|
break;
|
||||||
|
case 'dot':
|
||||||
|
this.dot();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cross() {
|
||||||
|
// Horizontal line
|
||||||
|
this.ctx.beginPath();
|
||||||
|
this.ctx.moveTo(this.mouseX - this.size, this.mouseY);
|
||||||
|
this.ctx.lineTo(this.mouseX - this.gapSize, this.mouseY);
|
||||||
|
this.ctx.stroke();
|
||||||
|
|
||||||
|
this.ctx.beginPath();
|
||||||
|
this.ctx.moveTo(this.mouseX + this.gapSize, this.mouseY);
|
||||||
|
this.ctx.lineTo(this.mouseX + this.size, this.mouseY);
|
||||||
|
this.ctx.stroke();
|
||||||
|
|
||||||
|
// Vertical line
|
||||||
|
this.ctx.beginPath();
|
||||||
|
this.ctx.moveTo(this.mouseX, this.mouseY - this.size);
|
||||||
|
this.ctx.lineTo(this.mouseX, this.mouseY - this.gapSize);
|
||||||
|
this.ctx.stroke();
|
||||||
|
|
||||||
|
this.ctx.beginPath();
|
||||||
|
this.ctx.moveTo(this.mouseX, this.mouseY + this.gapSize);
|
||||||
|
this.ctx.lineTo(this.mouseX, this.mouseY + this.size);
|
||||||
|
this.ctx.stroke();
|
||||||
|
}
|
||||||
|
|
||||||
|
circle() {
|
||||||
|
// Outer circle
|
||||||
|
this.ctx.beginPath();
|
||||||
|
this.ctx.arc(this.mouseX, this.mouseY, this.size, 0, Math.PI * 2);
|
||||||
|
this.ctx.stroke();
|
||||||
|
|
||||||
|
// Inner dot
|
||||||
|
this.ctx.fillStyle = this.color;
|
||||||
|
this.ctx.beginPath();
|
||||||
|
this.ctx.arc(this.mouseX, this.mouseY, this.thickness * 1.5, 0, Math.PI * 2);
|
||||||
|
this.ctx.fill();
|
||||||
|
}
|
||||||
|
|
||||||
|
dot() {
|
||||||
|
this.ctx.fillStyle = this.color;
|
||||||
|
this.ctx.beginPath();
|
||||||
|
this.ctx.arc(this.mouseX, this.mouseY, this.size / 4, 0, Math.PI * 2);
|
||||||
|
this.ctx.fill();
|
||||||
|
}
|
||||||
|
|
||||||
|
setCrosshairStyle(style) {
|
||||||
|
this.style = style;
|
||||||
|
}
|
||||||
|
|
||||||
|
setColor(color) {
|
||||||
|
this.color = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
setSymbolStyle(style) {
|
||||||
|
this.symbolStyle = style;
|
||||||
|
}
|
||||||
|
|
||||||
|
setSymbolColor(color) {
|
||||||
|
this.symbolColor = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
setLineVisible(visible) {
|
||||||
|
this.showConnector = visible;
|
||||||
|
}
|
||||||
|
}
|
||||||
79
source/screens/demo/examples/game/fpv.liquid
Normal file
79
source/screens/demo/examples/game/fpv.liquid
Normal file
|
|
@ -0,0 +1,79 @@
|
||||||
|
---
|
||||||
|
title: FPV
|
||||||
|
tags:
|
||||||
|
- game
|
||||||
|
---
|
||||||
|
{% assign bodyClass = 'body_fpv' -%}
|
||||||
|
{% layout 'hippie/simple.liquid' %}
|
||||||
|
|
||||||
|
{% block links %}
|
||||||
|
{{ block.super -}}
|
||||||
|
<link href="/vendor/bootstrap-icons/font/bootstrap-icons.min.css" rel="stylesheet">
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block body %}
|
||||||
|
<canvas id="view"></canvas>
|
||||||
|
|
||||||
|
<header class="io controls">
|
||||||
|
<nav>
|
||||||
|
<span>Color</span>
|
||||||
|
<button onclick="changeColor('black')">Black</button>
|
||||||
|
<button onclick="changeColor('white')">White</button>
|
||||||
|
<button onclick="changeColor('crimson')">Red</button>
|
||||||
|
<button onclick="changeColor('#00ffff')">Cyan</button>
|
||||||
|
</nav>
|
||||||
|
<nav>
|
||||||
|
<span>Crosshair</span>
|
||||||
|
<button onclick="changeCrosshairStyle('cross')"><i class="bi bi-plus-lg"></i></button>
|
||||||
|
<button onclick="changeCrosshairStyle('circle')"><i class="bi bi-circle"></i></button>
|
||||||
|
<button onclick="changeCrosshairStyle('dot')"><i class="bi bi-dot"></i></button>
|
||||||
|
</nav>
|
||||||
|
<nav>
|
||||||
|
<span>Connector</span>
|
||||||
|
<button onclick="toggleLine()">Toggle</button>
|
||||||
|
<hr class="vertical">
|
||||||
|
<button onclick="changeSymbolStyle('arrow')"><i class="bi bi-caret-up-fill"></i></button>
|
||||||
|
<button onclick="changeSymbolStyle('square')"><i class="bi bi-square-fill"></i></button>
|
||||||
|
<button onclick="changeSymbolStyle('circle')"><i class="bi bi-circle-fill"></i></button>
|
||||||
|
<button onclick="changeSymbolStyle('diamond')"><i class="bi bi-diamond-fill"></i></button>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block assets %}
|
||||||
|
<script src="/js/game.js"></script>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block script %}
|
||||||
|
<script>
|
||||||
|
const canvas = document.getElementById('view');
|
||||||
|
canvas.width = window.innerWidth;
|
||||||
|
canvas.height = window.innerHeight;
|
||||||
|
|
||||||
|
const crosshair = new HippieCrosshair(canvas);
|
||||||
|
|
||||||
|
function changeCrosshairStyle(style) {
|
||||||
|
crosshair.setCrosshairStyle(style);
|
||||||
|
}
|
||||||
|
|
||||||
|
function changeSymbolStyle(style) {
|
||||||
|
crosshair.setSymbolStyle(style);
|
||||||
|
}
|
||||||
|
|
||||||
|
function changeColor(color) {
|
||||||
|
crosshair.setColor(color);
|
||||||
|
crosshair.setSymbolColor(color);
|
||||||
|
crosshair.lineColor = `rgba(${parseInt(color.slice(1, 3), 16)}, ${parseInt(color.slice(3, 5), 16)}, ${parseInt(color.slice(5, 7), 16)}, 0.3)`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleLine() {
|
||||||
|
crosshair.setLineVisible(!crosshair.showConnector);
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('resize', () => {
|
||||||
|
canvas.width = window.innerWidth;
|
||||||
|
canvas.height = window.innerHeight;
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
@use "sass:map";
|
||||||
|
|
||||||
@use "../hippie-style/hippie";
|
@use "../hippie-style/hippie";
|
||||||
|
|
||||||
.body_game {
|
.body_game {
|
||||||
|
|
@ -24,4 +26,21 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.body_fpv {
|
||||||
|
@extend .h_full_view;
|
||||||
|
|
||||||
|
canvas {
|
||||||
|
display: block;
|
||||||
|
cursor: none;
|
||||||
|
background-color: lightblue;
|
||||||
|
}
|
||||||
|
|
||||||
|
.controls {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
border-radius: hippie.$radius_basic;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue