added joystick and attack button is moved to right

This commit is contained in:
Ariari04 2026-01-14 22:39:38 +06:00
parent b7f5f43777
commit 3bd3202bf8
6 changed files with 298 additions and 37 deletions

View File

@ -154,8 +154,8 @@
{
"type": "Button",
"name": "shootButton",
"x": 100,
"y": 100,
"x": 1115,
"y": 0,
"width": 100,
"height": 100,
"textures": {

BIN
resources/joystick_base.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
resources/joystick_knob.png (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -1,4 +1,4 @@
#include "Game.h"
#include "Game.h"
#include "AnimatedModel.h"
#include "BoneAnimatedModel.h"
#include "utils/Utils.h"
@ -281,6 +281,13 @@ namespace ZL
Environment::shipVelocity = value * 1000.f;
});
// Добавляем джойстик для управления кораблём
// centerX=150, centerY=150 (от левого нижнего угла в UI координатах)
// baseRadius=120, knobRadius=50
uiManager.addJoystick("shipJoystick", 150.0f, 150.0f, 120.0f, 50.0f,
renderer, CONST_ZIP_FILE,
"resources/joystick_base.png", "resources/joystick_knob.png");
/*uiManager.setSliderCallback("musicVolumeSlider", [this](const std::string& name, float value) {
std::cerr << "Music volume slider changed to: " << value << std::endl;
musicVolume = value;
@ -604,18 +611,20 @@ namespace ZL
sparkEmitter.update(static_cast<float>(delta));
planetObject.update(static_cast<float>(delta));
if (Environment::tapDownHold && !uiManager.isUiInteraction())
{
float diffx = Environment::tapDownCurrentPos(0) - Environment::tapDownStartPos(0);
float diffy = Environment::tapDownCurrentPos(1) - Environment::tapDownStartPos(1);
// Управление кораблём через джойстик
auto joystick = uiManager.findJoystick("shipJoystick");
if (joystick && joystick->isActive) {
float joyX = joystick->getDirectionX(); // -1..1
float joyY = joystick->getDirectionY(); // -1..1
float magnitude = joystick->getMagnitude(); // 0..1
if (isDraggingShip) {
if (std::abs(diffy) > 5.0f || std::abs(diffx) > 5.0f)
{
float velocity = std::sqrt(diffx * diffx + diffy * diffy);
Environment::shipVelocity = velocity * static_cast<float>(delta) / 100.0f;
if (magnitude > 0.1f) {
// Скорость пропорциональна отклонению джойстика
Environment::shipVelocity = magnitude * 500.0f * static_cast<float>(delta) / 100.0f;
float angleY = std::atan2(-diffx, -diffy);
// Угол поворота корабля на основе направления джойстика
// joyY отрицательный = вперёд, joyX = влево/вправо
float angleY = std::atan2(-joyX, -joyY);
Eigen::Quaternionf q(Eigen::AngleAxisf(angleY, Eigen::Vector3f::UnitY()));
rotateShipMat = q.toRotationMatrix();
@ -629,11 +638,17 @@ namespace ZL
Environment::shipVelocity = 0.0f;
}
}
else if (isDraggingCamera) {
else if (Environment::tapDownHold && !uiManager.isUiInteraction())
{
float diffx = Environment::tapDownCurrentPos(0) - Environment::tapDownStartPos(0);
float diffy = Environment::tapDownCurrentPos(1) - Environment::tapDownStartPos(1);
// Только управление камерой через drag (вне зоны джойстика)
if (isDraggingCamera) {
if (std::abs(diffy) > 5.0f || std::abs(diffx) > 5.0f)
{
float rotationPower = std::sqrt(diffx * diffx + diffy * diffy);
float deltaAlpha = rotationPower * static_cast<float>(delta) * static_cast<float>(M_PI) / 500000.f;
float deltaAlpha = rotationPower * static_cast<float>(delta) * static_cast<float>(M_PI) / 150000.f;
Eigen::Vector3f rotationDirection(diffy, diffx, 0.0f);
rotationDirection.normalize();
@ -877,25 +892,30 @@ namespace ZL
}();
if (!uiManager.isUiInteraction()) {
if (mx < 400 && my > 400) {
isDraggingShip = true;
isDraggingCamera = false;
shipMoveLockActive = false; // новый drag -> новый lock
}
else {
// Джойстик обрабатывает управление кораблём, поэтому
// любое нажатие вне UI элементов - это управление камерой
isDraggingShip = false;
isDraggingCamera = true;
}
}
}
void Game::handleUp(int mx, int my)
{
int uiX = mx;
int uiY = Environment::height - my;
// Проверяем был ли активен джойстик до отпускания
auto joystick = uiManager.findJoystick("shipJoystick");
bool wasJoystickActive = joystick && joystick->isActive;
uiManager.onMouseUp(uiX, uiY);
// Сбрасываем состояние если отпустили джойстик
if (wasJoystickActive) {
Environment::shipVelocity = 0.0f;
shipMoveLockActive = false;
rotateShipMat = Matrix3f::Identity();
}
if (!uiManager.isUiInteraction()) {
Environment::tapDownHold = false;
Environment::shipVelocity = 0.0f;

View File

@ -167,6 +167,91 @@ namespace ZL {
renderer.DisableVertexAttribArray(vTexCoordName);
}
void UiJoystick::buildBaseMesh() {
baseMesh.data.PositionData.clear();
baseMesh.data.TexCoordData.clear();
float x0 = centerX - baseRadius;
float y0 = centerY - baseRadius;
float x1 = centerX + baseRadius;
float y1 = centerY + baseRadius;
baseMesh.data.PositionData.push_back({ x0, y0, 0 });
baseMesh.data.TexCoordData.push_back({ 0, 0 });
baseMesh.data.PositionData.push_back({ x0, y1, 0 });
baseMesh.data.TexCoordData.push_back({ 0, 1 });
baseMesh.data.PositionData.push_back({ x1, y1, 0 });
baseMesh.data.TexCoordData.push_back({ 1, 1 });
baseMesh.data.PositionData.push_back({ x0, y0, 0 });
baseMesh.data.TexCoordData.push_back({ 0, 0 });
baseMesh.data.PositionData.push_back({ x1, y1, 0 });
baseMesh.data.TexCoordData.push_back({ 1, 1 });
baseMesh.data.PositionData.push_back({ x1, y0, 0 });
baseMesh.data.TexCoordData.push_back({ 1, 0 });
baseMesh.RefreshVBO();
}
void UiJoystick::buildKnobMesh() {
knobMesh.data.PositionData.clear();
knobMesh.data.TexCoordData.clear();
float cx = centerX + knobOffsetX;
float cy = centerY + knobOffsetY;
float x0 = cx - knobRadius;
float y0 = cy - knobRadius;
float x1 = cx + knobRadius;
float y1 = cy + knobRadius;
knobMesh.data.PositionData.push_back({ x0, y0, 0 });
knobMesh.data.TexCoordData.push_back({ 0, 0 });
knobMesh.data.PositionData.push_back({ x0, y1, 0 });
knobMesh.data.TexCoordData.push_back({ 0, 1 });
knobMesh.data.PositionData.push_back({ x1, y1, 0 });
knobMesh.data.TexCoordData.push_back({ 1, 1 });
knobMesh.data.PositionData.push_back({ x0, y0, 0 });
knobMesh.data.TexCoordData.push_back({ 0, 0 });
knobMesh.data.PositionData.push_back({ x1, y1, 0 });
knobMesh.data.TexCoordData.push_back({ 1, 1 });
knobMesh.data.PositionData.push_back({ x1, y0, 0 });
knobMesh.data.TexCoordData.push_back({ 1, 0 });
knobMesh.RefreshVBO();
}
void UiJoystick::draw(Renderer& renderer) const {
static const std::string vPositionName = "vPosition";
static const std::string vTexCoordName = "vTexCoord";
static const std::string textureUniformName = "Texture";
renderer.RenderUniform1i(textureUniformName, 0);
renderer.EnableVertexAttribArray(vPositionName);
renderer.EnableVertexAttribArray(vTexCoordName);
if (texBase) {
glBindTexture(GL_TEXTURE_2D, texBase->getTexID());
renderer.DrawVertexRenderStruct(baseMesh);
}
if (texKnob) {
glBindTexture(GL_TEXTURE_2D, texKnob->getTexID());
renderer.DrawVertexRenderStruct(knobMesh);
}
renderer.DisableVertexAttribArray(vPositionName);
renderer.DisableVertexAttribArray(vTexCoordName);
}
void UiManager::loadFromFile(const std::string& path, Renderer& renderer, const std::string& zipFile) {
std::string content;
try {
@ -446,6 +531,57 @@ namespace ZL {
return true;
}
bool UiManager::addJoystick(const std::string& name, float centerX, float centerY, float baseRadius, float knobRadius,
Renderer& renderer, const std::string& zipFile,
const std::string& basePath, const std::string& knobPath) {
auto j = std::make_shared<UiJoystick>();
j->name = name;
j->centerX = centerX;
j->centerY = centerY;
j->baseRadius = baseRadius;
j->knobRadius = knobRadius;
j->knobOffsetX = 0;
j->knobOffsetY = 0;
j->isActive = false;
try {
if (!basePath.empty()) {
auto data = CreateTextureDataFromPng(basePath.c_str(), zipFile.c_str());
j->texBase = std::make_shared<Texture>(data);
}
if (!knobPath.empty()) {
auto data = CreateTextureDataFromPng(knobPath.c_str(), zipFile.c_str());
j->texKnob = std::make_shared<Texture>(data);
}
}
catch (const std::exception& e) {
std::cerr << "UiManager: addJoystick failed to load textures: " << e.what() << std::endl;
return false;
}
j->buildBaseMesh();
j->buildKnobMesh();
joysticks.push_back(j);
return true;
}
std::shared_ptr<UiJoystick> UiManager::findJoystick(const std::string& name) {
for (auto& j : joysticks) if (j->name == name) return j;
return nullptr;
}
void UiManager::resetJoystick(const std::string& name) {
auto j = findJoystick(name);
if (j) {
j->knobOffsetX = 0;
j->knobOffsetY = 0;
j->isActive = false;
j->buildKnobMesh();
}
}
bool UiManager::pushMenuFromFile(const std::string& path, Renderer& renderer, const std::string& zipFile) {
MenuState prev;
prev.root = root;
@ -526,6 +662,9 @@ namespace ZL {
renderer.PushMatrix();
renderer.LoadIdentity();
for (const auto& j : joysticks) {
j->draw(renderer);
}
for (const auto& b : buttons) {
b->draw(renderer);
}
@ -724,6 +863,22 @@ namespace ZL {
s->buildKnobMesh();
if (s->onValueChanged) s->onValueChanged(s->name, s->value);
}
if (pressedJoystick) {
auto j = pressedJoystick;
float dx = (float)x - j->centerX;
float dy = (float)y - j->centerY;
float dist = std::sqrt(dx * dx + dy * dy);
if (dist > j->baseRadius) {
dx = dx / dist * j->baseRadius;
dy = dy / dist * j->baseRadius;
}
j->knobOffsetX = dx;
j->knobOffsetY = dy;
j->buildKnobMesh();
}
}
void UiManager::onMouseDown(int x, int y) {
@ -752,6 +907,27 @@ namespace ZL {
break;
}
}
for (auto& j : joysticks) {
if (j->contains((float)x, (float)y)) {
pressedJoystick = j;
j->isActive = true;
float dx = (float)x - j->centerX;
float dy = (float)y - j->centerY;
float dist = std::sqrt(dx * dx + dy * dy);
if (dist > j->baseRadius) {
dx = dx / dist * j->baseRadius;
dy = dy / dist * j->baseRadius;
}
j->knobOffsetX = dx;
j->knobOffsetY = dy;
j->buildKnobMesh();
break;
}
}
}
void UiManager::onMouseUp(int x, int y) {
@ -771,6 +947,14 @@ namespace ZL {
if (pressedSlider) {
pressedSlider.reset();
}
if (pressedJoystick) {
pressedJoystick->knobOffsetX = 0;
pressedJoystick->knobOffsetY = 0;
pressedJoystick->isActive = false;
pressedJoystick->buildKnobMesh();
pressedJoystick.reset();
}
}
std::shared_ptr<UiButton> UiManager::findButton(const std::string& name) {

View File

@ -9,6 +9,8 @@
#include <memory>
#include <functional>
#include <map>
#include <cmath>
#include <algorithm>
namespace ZL {
@ -71,6 +73,47 @@ namespace ZL {
void draw(Renderer& renderer) const;
};
struct UiJoystick {
std::string name;
float centerX = 0; // центр джойстика (UI координаты)
float centerY = 0;
float baseRadius = 100; // радиус основания
float knobRadius = 40; // радиус ручки
float knobOffsetX = 0; // смещение ручки от центра
float knobOffsetY = 0;
bool isActive = false; // нажат ли джойстик
std::shared_ptr<Texture> texBase; // текстура основания
std::shared_ptr<Texture> texKnob; // текстура ручки
VertexRenderStruct baseMesh;
VertexRenderStruct knobMesh;
// Возвращает направление -1..1 по каждой оси
float getDirectionX() const { return baseRadius > 0 ? knobOffsetX / baseRadius : 0; }
float getDirectionY() const { return baseRadius > 0 ? -knobOffsetY / baseRadius : 0; }
// Возвращает силу 0..1
float getMagnitude() const {
if (baseRadius <= 0) return 0;
float dist = std::sqrt(knobOffsetX * knobOffsetX + knobOffsetY * knobOffsetY);
float ratio = dist / baseRadius;
return ratio < 1.0f ? ratio : 1.0f;
}
bool contains(float px, float py) const {
float dx = px - centerX;
float dy = py - centerY;
return (dx * dx + dy * dy) <= (baseRadius * baseRadius);
}
void buildBaseMesh();
void buildKnobMesh();
void draw(Renderer& renderer) const;
};
struct UiNode {
std::string type;
UiRect rect;
@ -108,7 +151,7 @@ namespace ZL {
void onMouseUp(int x, int y);
bool isUiInteraction() const {
return pressedButton != nullptr || pressedSlider != nullptr;
return pressedButton != nullptr || pressedSlider != nullptr || pressedJoystick != nullptr;
}
void stopAllAnimations() {
@ -135,6 +178,12 @@ namespace ZL {
bool setSliderCallback(const std::string& name, std::function<void(const std::string&, float)> cb);
bool setSliderValue(const std::string& name, float value); // programmatic set (clamped 0..1)
bool addJoystick(const std::string& name, float centerX, float centerY, float baseRadius, float knobRadius,
Renderer& renderer, const std::string& zipFile,
const std::string& basePath, const std::string& knobPath);
std::shared_ptr<UiJoystick> findJoystick(const std::string& name);
void resetJoystick(const std::string& name); // сбросить ручку в центр
bool pushMenuFromFile(const std::string& path, Renderer& renderer, const std::string& zipFile = "");
bool popMenu();
void clearMenuStack();
@ -176,12 +225,14 @@ namespace ZL {
std::shared_ptr<UiNode> root;
std::vector<std::shared_ptr<UiButton>> buttons;
std::vector<std::shared_ptr<UiSlider>> sliders;
std::vector<std::shared_ptr<UiJoystick>> joysticks;
std::map<std::shared_ptr<UiNode>, std::vector<ActiveAnim>> nodeActiveAnims;
std::map<std::pair<std::string, std::string>, std::function<void()>> animCallbacks; // key: (nodeName, animName)
std::shared_ptr<UiButton> pressedButton;
std::shared_ptr<UiSlider> pressedSlider;
std::shared_ptr<UiJoystick> pressedJoystick;
struct MenuState {
std::shared_ptr<UiNode> root;