Repaired hole minigame

This commit is contained in:
2026-04-26 12:55:21 +02:00
parent 74fa735322
commit abbe4842fe
26 changed files with 4471 additions and 282 deletions

8
Assets/ClientSDK.meta Normal file
View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 799f52449ae21404c9a7593f6dc28c60
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,285 @@
using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
namespace GeoSus.Client
{
// Klientská strana šifrování - generuje session key, šifruje RSA, AES-CBC session
// Používá AES-CBC místo AES-GCM pro kompatibilitu s Unity
public class ClientEncryption : IDisposable
{
private byte[] _sessionKey;
private byte[] _sessionIv;
private long _nonceCounter;
private readonly object _lock = new object();
// Kontrola, zda je session key nastaven
public bool HasSessionKey => _sessionKey != null && _sessionIv != null;
// Generuje nový session key a IV
public void GenerateSessionKey()
{
_sessionKey = new byte[32]; // AES-256
_sessionIv = new byte[16]; // CBC IV (16 bytes)
using (var rng = RandomNumberGenerator.Create())
{
rng.GetBytes(_sessionKey);
rng.GetBytes(_sessionIv);
}
}
public byte[] SessionKey => _sessionKey ?? throw new InvalidOperationException("Session key not generated");
public byte[] SessionIV => _sessionIv ?? throw new InvalidOperationException("Session IV not generated");
// Zašifruje session key pomocí RSA public key serveru
public (string EncryptedKey, string EncryptedIV) EncryptSessionKeyForServer(string rsaPublicKeyPem)
{
if (_sessionKey == null || _sessionIv == null)
throw new InvalidOperationException("Session key not generated");
using (var rsa = RSA.Create())
{
// Parse PEM - extrahuj Base64 obsah
var pemLines = rsaPublicKeyPem.Split('\n');
var base64 = new StringBuilder();
foreach (var line in pemLines)
{
var trimmed = line.Trim();
if (!trimmed.StartsWith("-----") && !string.IsNullOrEmpty(trimmed))
{
base64.Append(trimmed);
}
}
var keyBytes = Convert.FromBase64String(base64.ToString());
// Unity kompatibilní import - parsujeme SubjectPublicKeyInfo ručně
ImportSubjectPublicKeyInfoManual(rsa, keyBytes);
// Používáme OaepSHA1 pro Unity kompatibilitu (OaepSHA256 není podporován)
var encryptedKey = rsa.Encrypt(_sessionKey, RSAEncryptionPadding.OaepSHA1);
var encryptedIv = rsa.Encrypt(_sessionIv, RSAEncryptionPadding.OaepSHA1);
return (Convert.ToBase64String(encryptedKey), Convert.ToBase64String(encryptedIv));
}
}
// Ručně parsuje SubjectPublicKeyInfo (DER) a importuje RSA klíč - Unity kompatibilní
private static void ImportSubjectPublicKeyInfoManual(RSA rsa, byte[] subjectPublicKeyInfo)
{
// SubjectPublicKeyInfo ::= SEQUENCE {
// algorithm AlgorithmIdentifier,
// subjectPublicKey BIT STRING }
// RSAPublicKey ::= SEQUENCE { modulus INTEGER, publicExponent INTEGER }
int index = 0;
// Outer SEQUENCE
if (subjectPublicKeyInfo[index++] != 0x30)
throw new InvalidOperationException("Invalid SubjectPublicKeyInfo");
ReadLength(subjectPublicKeyInfo, ref index);
// AlgorithmIdentifier SEQUENCE - skip it
if (subjectPublicKeyInfo[index++] != 0x30)
throw new InvalidOperationException("Invalid AlgorithmIdentifier");
int algLen = ReadLength(subjectPublicKeyInfo, ref index);
index += algLen;
// BIT STRING containing RSAPublicKey
if (subjectPublicKeyInfo[index++] != 0x03)
throw new InvalidOperationException("Invalid BIT STRING");
ReadLength(subjectPublicKeyInfo, ref index);
index++; // Skip unused bits byte (should be 0)
// RSAPublicKey SEQUENCE
if (subjectPublicKeyInfo[index++] != 0x30)
throw new InvalidOperationException("Invalid RSAPublicKey");
ReadLength(subjectPublicKeyInfo, ref index);
// Modulus INTEGER
byte[] modulus = ReadInteger(subjectPublicKeyInfo, ref index);
// Exponent INTEGER
byte[] exponent = ReadInteger(subjectPublicKeyInfo, ref index);
var parameters = new RSAParameters
{
Modulus = modulus,
Exponent = exponent
};
rsa.ImportParameters(parameters);
}
private static int ReadLength(byte[] data, ref int index)
{
int length = data[index++];
if ((length & 0x80) != 0)
{
int numBytes = length & 0x7F;
length = 0;
for (int i = 0; i < numBytes; i++)
{
length = (length << 8) | data[index++];
}
}
return length;
}
private static byte[] ReadInteger(byte[] data, ref int index)
{
if (data[index++] != 0x02)
throw new InvalidOperationException("Expected INTEGER");
int length = ReadLength(data, ref index);
// Skip leading zero if present (used for positive sign in DER)
int originalLength = length;
int start = index;
if (length > 1 && data[start] == 0x00)
{
start++;
length--;
}
byte[] result = new byte[length];
Buffer.BlockCopy(data, start, result, 0, length);
index += originalLength;
return result;
}
// Šifruje zprávu pomocí AES-256-CBC s HMAC
public byte[] Encrypt(byte[] plaintext)
{
if (_sessionKey == null || _sessionIv == null)
throw new InvalidOperationException("Session key not set");
lock (_lock)
{
// Generuj unikátní IV pro tuto zprávu
var iv = GetNextIV();
using (var aes = Aes.Create())
{
aes.Key = _sessionKey;
aes.IV = iv;
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.PKCS7;
byte[] ciphertext;
using (var encryptor = aes.CreateEncryptor())
using (var ms = new MemoryStream())
{
using (var cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write))
{
cs.Write(plaintext, 0, plaintext.Length);
}
ciphertext = ms.ToArray();
}
// Compute HMAC pro integritu
byte[] hmac;
using (var hmacSha = new HMACSHA256(_sessionKey))
{
var toSign = new byte[iv.Length + ciphertext.Length];
Buffer.BlockCopy(iv, 0, toSign, 0, iv.Length);
Buffer.BlockCopy(ciphertext, 0, toSign, iv.Length, ciphertext.Length);
hmac = hmacSha.ComputeHash(toSign);
}
// Výstup: [16 bytes IV][32 bytes HMAC][ciphertext]
var result = new byte[16 + 32 + ciphertext.Length];
Buffer.BlockCopy(iv, 0, result, 0, 16);
Buffer.BlockCopy(hmac, 0, result, 16, 32);
Buffer.BlockCopy(ciphertext, 0, result, 48, ciphertext.Length);
return result;
}
}
}
// Dešifruje zprávu pomocí AES-256-CBC s HMAC ověřením
public byte[] Decrypt(byte[] encrypted)
{
if (_sessionKey == null)
throw new InvalidOperationException("Session key not set");
if (encrypted.Length < 48) return null;
try
{
var iv = new byte[16];
var hmac = new byte[32];
var ciphertext = new byte[encrypted.Length - 48];
Buffer.BlockCopy(encrypted, 0, iv, 0, 16);
Buffer.BlockCopy(encrypted, 16, hmac, 0, 32);
Buffer.BlockCopy(encrypted, 48, ciphertext, 0, ciphertext.Length);
// Ověř HMAC
byte[] expectedHmac;
using (var hmacSha = new HMACSHA256(_sessionKey))
{
var toVerify = new byte[iv.Length + ciphertext.Length];
Buffer.BlockCopy(iv, 0, toVerify, 0, iv.Length);
Buffer.BlockCopy(ciphertext, 0, toVerify, iv.Length, ciphertext.Length);
expectedHmac = hmacSha.ComputeHash(toVerify);
}
// Constant-time compare
var diff = 0;
for (int i = 0; i < 32; i++)
{
diff |= hmac[i] ^ expectedHmac[i];
}
if (diff != 0) return null; // HMAC mismatch
using (var aes = Aes.Create())
{
aes.Key = _sessionKey;
aes.IV = iv;
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.PKCS7;
using (var decryptor = aes.CreateDecryptor())
using (var ms = new MemoryStream(ciphertext))
using (var cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Read))
using (var output = new MemoryStream())
{
cs.CopyTo(output);
return output.ToArray();
}
}
}
catch (CryptographicException)
{
return null;
}
}
private byte[] GetNextIV()
{
if (_sessionIv == null)
throw new InvalidOperationException("Session IV not set");
var iv = new byte[16];
Buffer.BlockCopy(_sessionIv, 0, iv, 0, 8);
var counter = System.Threading.Interlocked.Increment(ref _nonceCounter);
var counterBytes = BitConverter.GetBytes(counter);
Buffer.BlockCopy(counterBytes, 0, iv, 8, 8);
return iv;
}
public void Dispose()
{
if (_sessionKey != null)
{
Array.Clear(_sessionKey, 0, _sessionKey.Length);
_sessionKey = null;
}
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: bc06bb57786c7e142b06ec231e5cf709

View File

@@ -0,0 +1,73 @@
using System;
using System.Collections.Generic;
using System.Threading;
namespace GeoSus.Client
{
// Event dispatcher pro Unity main thread
// Unity může přidat SynchronizationContext, nebo polling z Update()
public class EventDispatcher
{
private readonly Queue<Action> _pendingActions = new Queue<Action>();
private readonly object _lock = new object();
private SynchronizationContext? _syncContext;
public EventDispatcher()
{
// Pokusíme se zachytit aktuální synchronization context (Unity main thread)
_syncContext = SynchronizationContext.Current;
}
// Volat z networking vlákna - naplánuje callback na main thread
public void Post(Action action)
{
if (_syncContext != null)
{
_syncContext.Post(_ => action(), null);
}
else
{
// Fallback - přidáme do fronty pro polling
lock (_lock)
{
_pendingActions.Enqueue(action);
}
}
}
// Volat z Unity Update() pokud není SynchronizationContext
public void ProcessPendingActions()
{
Action[] actions;
lock (_lock)
{
if (_pendingActions.Count == 0) return;
actions = _pendingActions.ToArray();
_pendingActions.Clear();
}
foreach (var action in actions)
{
try
{
action();
}
catch (Exception ex)
{
Console.WriteLine($"EventDispatcher error: {ex}");
}
}
}
public int PendingCount
{
get
{
lock (_lock)
{
return _pendingActions.Count;
}
}
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 1d2251b279edb0147bd274a884ac878b

View File

@@ -0,0 +1,607 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
namespace GeoSus.Client
{
// Hlavní klientská třída pro připojení k serveru
public class GameClient : IDisposable
{
private TcpClient? _tcpClient;
private NetworkStream? _stream;
private ClientEncryption? _encryption;
private CancellationTokenSource? _cts;
private Task? _receiveTask;
private int _clientSeq;
private readonly object _sendLock = new object();
private bool _handshakeComplete;
public string ClientUuid { get; }
public string DisplayName { get; set; }
public bool IsConnected => _tcpClient?.Connected ?? false;
public bool IsReady => IsConnected && _handshakeComplete && (_encryption?.HasSessionKey ?? false);
public EventDispatcher Dispatcher { get; }
// Events - voláno na main thread přes dispatcher
public event Action? OnConnected;
public event Action<string>? OnDisconnected;
public event Action<string>? OnError;
public event Action<Message>? OnMessage;
public event Action<GameEvent>? OnGameEvent;
// Lobby state
public string? LobbyId { get; private set; }
public string? JoinCode { get; private set; }
public LobbyState? CurrentLobbyState { get; private set; }
public PlayerRole? MyRole { get; private set; }
public List<GameTask> MyTasks { get; } = new List<GameTask>();
public Position MyPosition { get; set; }
public Dictionary<string, PlayerPositionInfo> PlayerPositions { get; } = new Dictionary<string, PlayerPositionInfo>();
public List<Body> Bodies { get; } = new List<Body>();
public int Ping { get; private set; }
public long LastEventId { get; private set; }
/// <summary>Returns true if this client is the current lobby owner</summary>
public bool IsOwner => CurrentLobbyState?.OwnerId == ClientUuid;
public GameClient(string clientUuid, string displayName)
{
ClientUuid = clientUuid;
DisplayName = displayName;
Dispatcher = new EventDispatcher();
}
#region Connection
public async Task<bool> ConnectAsync(string host, int port)
{
try
{
_tcpClient = new TcpClient();
await _tcpClient.ConnectAsync(host, port);
_stream = _tcpClient.GetStream();
_encryption = new ClientEncryption();
_cts = new CancellationTokenSource();
// Handshake
if (!await PerformHandshakeAsync())
{
Disconnect("Handshake failed");
return false;
}
// Spustíme příjem zpráv
_receiveTask = Task.Run(() => ReceiveLoopAsync(_cts.Token));
Dispatcher.Post(() => OnConnected?.Invoke());
return true;
}
catch (Exception ex)
{
Dispatcher.Post(() => OnError?.Invoke(ex.Message));
return false;
}
}
private async Task<bool> PerformHandshakeAsync()
{
if (_stream == null || _encryption == null) return false;
// 1. ClientHello
var hello = new ClientHello
{
ClientUuid = ClientUuid,
DisplayName = DisplayName
};
await SendPlainAsync(hello);
// 2. ServerHello
var serverHelloData = await ReadMessageAsync();
if (serverHelloData == null) return false;
var serverHello = MessageSerializer.Deserialize(serverHelloData) as ServerHello;
if (serverHello == null) return false;
// 3. Generujeme session key a šifrujeme RSA
_encryption.GenerateSessionKey();
var (encKey, encIv) = _encryption.EncryptSessionKeyForServer(serverHello.RsaPublicKeyPem);
var keyExchange = new KeyExchange
{
EncryptedSessionKey = encKey,
EncryptedIV = encIv
};
await SendPlainAsync(keyExchange);
// 4. KeyExchangeAck (šifrovaně)
var ackData = await ReadMessageAsync();
if (ackData == null) return false;
var decrypted = _encryption.Decrypt(ackData);
if (decrypted == null) return false;
var ack = MessageSerializer.Deserialize(decrypted) as KeyExchangeAck;
if (ack?.Status == "success")
{
_handshakeComplete = true;
return true;
}
return false;
}
public void Disconnect(string reason = "User disconnected")
{
_cts?.Cancel();
_tcpClient?.Close();
_tcpClient = null;
_stream = null;
_encryption?.Dispose();
_encryption = null;
LobbyId = null;
JoinCode = null;
CurrentLobbyState = null;
MyRole = null;
MyTasks.Clear();
PlayerPositions.Clear();
Bodies.Clear();
Dispatcher.Post(() => OnDisconnected?.Invoke(reason));
}
#endregion
#region Sending
public void Send(Message message)
{
if (_stream == null || _encryption == null || !IsConnected) return;
message.ClientSeq = Interlocked.Increment(ref _clientSeq);
if (string.IsNullOrEmpty(message.ActionId))
{
message.ActionId = Guid.NewGuid().ToString("N").Substring(0, 8);
}
var plain = MessageSerializer.Serialize(message);
var encrypted = _encryption.Encrypt(plain);
lock (_sendLock)
{
try
{
SendData(encrypted);
}
catch (Exception ex)
{
Dispatcher.Post(() => OnError?.Invoke($"Send error: {ex.Message}"));
}
}
}
private async Task SendPlainAsync(Message message)
{
if (_stream == null) return;
var data = MessageSerializer.Serialize(message);
await SendDataAsync(data);
}
private void SendData(byte[] data)
{
if (_stream == null) return;
var lengthBuffer = BitConverter.GetBytes(data.Length);
if (BitConverter.IsLittleEndian)
Array.Reverse(lengthBuffer);
_stream.Write(lengthBuffer, 0, 4);
_stream.Write(data, 0, data.Length);
_stream.Flush();
}
private async Task SendDataAsync(byte[] data)
{
if (_stream == null) return;
var lengthBuffer = BitConverter.GetBytes(data.Length);
if (BitConverter.IsLittleEndian)
Array.Reverse(lengthBuffer);
await _stream.WriteAsync(lengthBuffer, 0, 4);
await _stream.WriteAsync(data, 0, data.Length);
await _stream.FlushAsync();
}
#endregion
#region Receiving
private async Task ReceiveLoopAsync(CancellationToken ct)
{
int decryptFailures = 0;
try
{
while (!ct.IsCancellationRequested && IsConnected)
{
var data = await ReadMessageAsync();
if (data == null) break;
var decrypted = _encryption?.Decrypt(data);
if (decrypted == null)
{
decryptFailures++;
if (decryptFailures >= 3)
{
Disconnect("Too many decryption failures");
return;
}
continue;
}
decryptFailures = 0;
var message = MessageSerializer.Deserialize(decrypted);
if (message != null)
{
ProcessMessage(message);
}
}
}
catch (Exception ex) when (!ct.IsCancellationRequested)
{
Disconnect($"Connection error: {ex.Message}");
}
}
private async Task<byte[]?> ReadMessageAsync()
{
if (_stream == null) return null;
var lengthBuffer = new byte[4];
var read = await _stream.ReadAsync(lengthBuffer, 0, 4);
if (read < 4) return null;
if (BitConverter.IsLittleEndian)
Array.Reverse(lengthBuffer);
var length = BitConverter.ToInt32(lengthBuffer, 0);
if (length <= 0 || length > 1048576) return null;
var buffer = new byte[length];
var totalRead = 0;
while (totalRead < length)
{
read = await _stream.ReadAsync(buffer, totalRead, length - totalRead);
if (read == 0) return null;
totalRead += read;
}
return buffer;
}
private void ProcessMessage(Message message)
{
// Zpracujeme speciální typy
switch (message)
{
case CreateLobbyResponse r:
if (r.Success)
{
LobbyId = r.LobbyId;
JoinCode = r.JoinCode;
CurrentLobbyState = r.LobbyState;
}
break;
case JoinLobbyResponse r:
if (r.Success)
{
LobbyId = r.LobbyId;
CurrentLobbyState = r.LobbyState;
JoinCode = r.LobbyState?.JoinCode;
}
break;
case PositionBroadcast b:
ProcessPositionBroadcast(b);
break;
case Pong p:
var now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
Ping = (int)(now - p.ClientTime);
break;
case GameEvent evt:
ProcessGameEvent(evt);
Dispatcher.Post(() => OnGameEvent?.Invoke(evt));
break;
}
Dispatcher.Post(() => OnMessage?.Invoke(message));
}
private void ProcessPositionBroadcast(PositionBroadcast broadcast)
{
PlayerPositions.Clear();
foreach (var player in broadcast.Players)
{
PlayerPositions[player.ClientUuid] = player;
}
}
private void ProcessGameEvent(GameEvent evt)
{
LastEventId = evt.EventId;
switch (evt.EventType)
{
case "PlayerJoined":
// Add player to lobby state
var joinedPayload = evt.GetPayload<PlayerJoinedPayload>();
if (joinedPayload != null && CurrentLobbyState?.Players != null)
{
// Check if player already exists
bool exists = CurrentLobbyState.Players.Any(p => p.ClientUuid == joinedPayload.ClientUuid);
if (!exists)
{
CurrentLobbyState.Players.Add(new PlayerInfo
{
ClientUuid = joinedPayload.ClientUuid,
DisplayName = joinedPayload.DisplayName,
IsOwner = false,
IsReady = false,
State = PlayerState.Alive
});
}
}
break;
case "PlayerLeft":
// Remove player from lobby state
var leftPayload = evt.GetPayload<PlayerLeftPayload>();
if (leftPayload != null && CurrentLobbyState?.Players != null)
{
CurrentLobbyState.Players.RemoveAll(p => p.ClientUuid == leftPayload.ClientUuid);
}
break;
case "HostChanged":
// Update lobby owner
var hostPayload = evt.GetPayload<HostChangedPayload>();
if (hostPayload != null && CurrentLobbyState != null)
{
CurrentLobbyState.OwnerId = hostPayload.NewHostId;
// Update IsOwner flag on all players
foreach (var player in CurrentLobbyState.Players)
{
player.IsOwner = player.ClientUuid == hostPayload.NewHostId;
}
}
break;
case "GameStarting":
// Game is entering loading phase - update lobby state if available
if (CurrentLobbyState != null)
{
CurrentLobbyState.Phase = GamePhase.Loading;
}
break;
case "MapDataReady":
// Map data received - store it and send confirmation
var mapDataPayload = evt.GetPayload<MapDataReadyPayload>();
if (mapDataPayload != null && CurrentLobbyState != null)
{
CurrentLobbyState.MapData = mapDataPayload.MapData;
CurrentLobbyState.MapDataReady = true;
}
// Send confirmation to server
Send(new MapDataReceived());
break;
case "GameStarted":
// Game officially started - update phase
if (CurrentLobbyState != null)
{
CurrentLobbyState.Phase = GamePhase.Playing;
}
break;
case "RoleAssigned":
var rolePayload = evt.GetPayload<RoleAssignedPayload>();
if (rolePayload != null && rolePayload.ClientUuid == ClientUuid)
{
MyRole = rolePayload.Role;
MyTasks.Clear();
if (rolePayload.Tasks != null)
{
MyTasks.AddRange(rolePayload.Tasks);
}
}
break;
case "PlayerKilled":
var killPayload = evt.GetPayload<PlayerKilledPayload>();
if (killPayload != null)
{
Bodies.Add(new Body
{
BodyId = killPayload.BodyId,
VictimId = killPayload.VictimId,
Location = killPayload.Location
});
}
break;
case "MeetingStarted":
if (CurrentLobbyState != null)
{
CurrentLobbyState.Phase = GamePhase.Meeting;
}
break;
case "VotingClosed":
Bodies.Clear(); // Bodies zmizí po meetingu
if (CurrentLobbyState != null)
{
CurrentLobbyState.Phase = GamePhase.Playing;
}
break;
case "GameEnded":
if (CurrentLobbyState != null)
{
CurrentLobbyState.Phase = GamePhase.Ended;
}
break;
}
}
#endregion
#region Game Actions
public void CreateLobby(Position? center = null, int impostorCount = 1, int taskCount = 5, string? password = null, double playAreaRadius = 500)
{
Send(new CreateLobby
{
PlayAreaCenter = center,
PlayAreaRadius = playAreaRadius,
ImpostorCount = impostorCount,
TaskCount = taskCount,
Password = password
});
}
public void JoinLobby(string joinCode, string? password = null)
{
Send(new JoinLobby
{
JoinCode = joinCode.ToUpperInvariant(),
Password = password
});
}
public void LeaveLobby()
{
Send(new LeaveLobby());
LobbyId = null;
JoinCode = null;
}
public void StartGame()
{
Send(new StartGame());
}
public void ReturnToLobby()
{
Send(new ReturnToLobby());
}
public void UpdatePosition(Position position)
{
MyPosition = position;
Send(new UpdatePosition { Position = position });
}
public void Kill(string targetUuid)
{
Send(new KillAttempt { TargetClientUuid = targetUuid });
}
public void ReportBody(string bodyId)
{
Send(new ReportBody { BodyId = bodyId });
}
public void CallEmergencyMeeting()
{
Send(new CallEmergencyMeeting());
}
public void Vote(string? targetUuid)
{
Send(new CastVote { TargetClientUuid = targetUuid });
}
/// <summary>
/// Pokus o dokončení tasku. Server ověří že hráč je na správné pozici.
/// </summary>
public void CompleteTask(string taskId)
{
Send(new TaskComplete { TaskId = taskId });
}
public void SendPing()
{
Send(new Ping { ClientTime = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() });
}
public void Reconnect(string lobbyId)
{
Send(new Reconnect { LobbyId = lobbyId, LastEventId = LastEventId });
}
#endregion
#region Helpers
public Body? FindNearbyBody(double maxDistance)
{
foreach (var body in Bodies)
{
if (MyPosition.DistanceTo(body.Location) <= maxDistance)
{
return body;
}
}
return null;
}
public string? FindNearbyPlayer(double maxDistance, bool aliveOnly = true)
{
foreach (var (uuid, info) in PlayerPositions)
{
if (uuid == ClientUuid) continue;
if (aliveOnly && info.State != PlayerState.Alive) continue;
if (MyPosition.DistanceTo(info.Position) <= maxDistance)
{
return uuid;
}
}
return null;
}
public GameTask? FindNearbyTask(double maxDistance)
{
foreach (var task in MyTasks)
{
if (MyPosition.DistanceTo(task.Location) <= maxDistance)
{
return task;
}
}
return null;
}
// Volat z Unity Update() pro zpracování callbacků
public void Update()
{
Dispatcher.ProcessPendingActions();
}
#endregion
public void Dispose()
{
Disconnect("Disposed");
_encryption?.Dispose();
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 91e0f647c37b0b94b83f53bb854db28c

1055
Assets/ClientSDK/Protocol.cs Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 14463228dfea2264ebfc36c3a7dc4b99

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 80ef0979df5d1fe489225f3e5edadc5c

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 3a4035bdb812fee4f96cb1aa1b24c999
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 131d9de257c8edc49991d792c6e702f6
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,7 +1,8 @@
//using GeoSus.Client;
using GeoSus.Client;
using System;
using UnityEngine;
public enum TaskType
{
Task //TODO: Typy úkolù
@@ -14,7 +15,7 @@ public interface ITask
public string TaskID { get; } // Unikátní ID úkolu pro server
public TaskType TaskType { get; } // Typ úkolu
public string TaskName { get; } // Viditelný název úkolu
public (double, double) TaskLocation { get; } // Polohy na mapì
public Position TaskLocation { get; } // Polohy na mapì
public bool IsCompleted { get; } // Stav dokonèení úkolu
void Initialize(Action<ITask> onCompleted); // Vytvoøení tasku + naètení postupu

View File

@@ -133,7 +133,7 @@ GameObject:
- component: {fileID: 6503151}
- component: {fileID: 6503150}
m_Layer: 0
m_Name: Cylinder
m_Name: SpecialHole
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
@@ -151,11 +151,8 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: ca7423fcca5f83249a2574cd84b7f806, type: 3}
m_Name:
m_EditorClassIdentifier: Assembly-CSharp::Hole
catchRadius: 1
catchRadius: 10
pullForce: 4
hasMovement: 0
moveSpeed: 2
moveRange: {x: 1.5, y: 0}
glowRenderer: {fileID: 0}
--- !u!136 &6503151
CapsuleCollider:
@@ -172,14 +169,14 @@ CapsuleCollider:
serializedVersion: 2
m_Bits: 0
m_LayerOverridePriority: 0
m_IsTrigger: 0
m_IsTrigger: 1
m_ProvidesContacts: 0
m_Enabled: 0
m_Enabled: 1
serializedVersion: 2
m_Radius: 0.5000001
m_Height: 2
m_Height: 50
m_Direction: 1
m_Center: {x: 0.000000059604645, y: 0, z: -0.00000008940697}
m_Center: {x: 0.00000006274173, y: 7.2227635, z: -0.0000000941126}
--- !u!23 &6503152
MeshRenderer:
m_ObjectHideFlags: 0
@@ -1600,179 +1597,38 @@ Transform:
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 50, y: 180, z: 0}
--- !u!1 &727261126
--- !u!1 &727261126 stripped
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_CorrespondingSourceObject: {fileID: 4463416604269783915, guid: 4c53ddc4a767a214e853d6ef8e5841a1, type: 3}
m_PrefabInstance: {fileID: 9074197596846296259}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 727261130}
- component: {fileID: 727261129}
- component: {fileID: 727261128}
- component: {fileID: 727261133}
- component: {fileID: 727261132}
- component: {fileID: 727261131}
m_Layer: 0
m_Name: Sphere
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!23 &727261128
MeshRenderer:
--- !u!54 &727261133
Rigidbody:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 727261126}
m_Enabled: 1
m_CastShadows: 1
m_ReceiveShadows: 1
m_DynamicOccludee: 1
m_StaticShadowCaster: 0
m_MotionVectors: 1
m_LightProbeUsage: 1
m_ReflectionProbeUsage: 1
m_RayTracingMode: 2
m_RayTraceProcedural: 0
m_RayTracingAccelStructBuildFlagsOverride: 0
m_RayTracingAccelStructBuildFlags: 1
m_SmallMeshCulling: 1
m_ForceMeshLod: -1
m_MeshLodSelectionBias: 0
m_RenderingLayerMask: 1
m_RendererPriority: 0
m_Materials:
- {fileID: 2100000, guid: 1a424fd36745ea14682570c13681319e, type: 2}
m_StaticBatchInfo:
firstSubMesh: 0
subMeshCount: 0
m_StaticBatchRoot: {fileID: 0}
m_ProbeAnchor: {fileID: 0}
m_LightProbeVolumeOverride: {fileID: 0}
m_ScaleInLightmap: 1
m_ReceiveGI: 1
m_PreserveUVs: 0
m_IgnoreNormalsForChartDetection: 0
m_ImportantGI: 0
m_StitchLightmapSeams: 1
m_SelectedEditorRenderState: 3
m_MinimumChartSize: 4
m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0}
m_GlobalIlluminationMeshLod: 0
m_SortingLayerID: 0
m_SortingLayer: 0
m_SortingOrder: 0
m_AdditionalVertexStreams: {fileID: 0}
--- !u!33 &727261129
MeshFilter:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 727261126}
m_Mesh: {fileID: 10207, guid: 0000000000000000e000000000000000, type: 0}
--- !u!4 &727261130
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 727261126}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: -13.138, y: -2.91, z: -0.63}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!50 &727261131
Rigidbody2D:
serializedVersion: 5
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 727261126}
m_BodyType: 0
m_Simulated: 1
m_UseFullKinematicContacts: 0
m_UseAutoMass: 0
m_Mass: 1
m_LinearDamping: 0
m_AngularDamping: 0.05
m_GravityScale: 1
m_Material: {fileID: 0}
m_CenterOfMass: {x: 0, y: 0, z: 0}
m_InertiaTensor: {x: 1, y: 1, z: 1}
m_InertiaRotation: {x: 0, y: 0, z: 0, w: 1}
m_IncludeLayers:
serializedVersion: 2
m_Bits: 0
m_ExcludeLayers:
serializedVersion: 2
m_Bits: 0
m_ImplicitCom: 1
m_ImplicitTensor: 1
m_UseGravity: 1
m_IsKinematic: 0
m_Interpolate: 0
m_SleepingMode: 1
m_CollisionDetection: 0
m_Constraints: 0
--- !u!114 &727261132
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 727261126}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: fb5157d7cd78450439c40cd6f5afe6ac, type: 3}
m_Name:
m_EditorClassIdentifier: Assembly-CSharp::DraggableObject
dragSmoothness: 15
spriteRenderer: {fileID: 0}
normalColor: {r: 1, g: 1, b: 1, a: 1}
dragColor: {r: 1, g: 1, b: 0.5, a: 1}
scaleOnDrag: 1.15
--- !u!70 &727261133
CapsuleCollider2D:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 727261126}
m_Enabled: 1
serializedVersion: 3
m_Density: 1
m_Material: {fileID: 0}
m_IncludeLayers:
serializedVersion: 2
m_Bits: 0
m_ExcludeLayers:
serializedVersion: 2
m_Bits: 0
m_LayerOverridePriority: 0
m_ForceSendLayers:
serializedVersion: 2
m_Bits: 4294967295
m_ForceReceiveLayers:
serializedVersion: 2
m_Bits: 4294967295
m_ContactCaptureLayers:
serializedVersion: 2
m_Bits: 4294967295
m_CallbackLayers:
serializedVersion: 2
m_Bits: 4294967295
m_IsTrigger: 1
m_UsedByEffector: 0
m_CompositeOperation: 0
m_CompositeOrder: 0
m_Offset: {x: 0, y: 0}
m_Size: {x: 1, y: 1}
m_Direction: 0
m_CollisionDetection: 0
--- !u!1 &776138489
GameObject:
m_ObjectHideFlags: 0
@@ -3068,7 +2924,7 @@ GameObject:
- component: {fileID: 1190939575}
- component: {fileID: 1190939574}
m_Layer: 0
m_Name: GameObject
m_Name: Spawner
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
@@ -3086,9 +2942,11 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: 071f79f81861c2741a92d8b044457d94, type: 3}
m_Name:
m_EditorClassIdentifier: Assembly-CSharp::ObjectSpawner
spawnedHoles:
- {fileID: 6503149}
- {fileID: 1386843746}
objectPrefabs:
- {fileID: 727261126}
holePrefab: {fileID: 6503149}
objectCount: 3
holeCount: 0
holesMove: 0
@@ -3097,8 +2955,6 @@ MonoBehaviour:
maxX: 3.5
minY: -5
maxY: 4
objectParent: {fileID: 727261130}
holeParent: {fileID: 6503154}
--- !u!4 &1190939575
Transform:
m_ObjectHideFlags: 0
@@ -3683,7 +3539,7 @@ GameObject:
- component: {fileID: 1386843748}
- component: {fileID: 1386843747}
m_Layer: 0
m_Name: Cylinder (1)
m_Name: ClassicHole
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
@@ -3701,11 +3557,8 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: ca7423fcca5f83249a2574cd84b7f806, type: 3}
m_Name:
m_EditorClassIdentifier: Assembly-CSharp::Hole
catchRadius: 1
catchRadius: 10
pullForce: 4
hasMovement: 0
moveSpeed: 2
moveRange: {x: 1.5, y: 0}
glowRenderer: {fileID: 0}
--- !u!136 &1386843748
CapsuleCollider:
@@ -3722,14 +3575,14 @@ CapsuleCollider:
serializedVersion: 2
m_Bits: 0
m_LayerOverridePriority: 0
m_IsTrigger: 0
m_IsTrigger: 1
m_ProvidesContacts: 0
m_Enabled: 0
m_Enabled: 1
serializedVersion: 2
m_Radius: 0.5000001
m_Height: 2
m_Height: 50
m_Direction: 1
m_Center: {x: 0.000000059604645, y: 0, z: -0.00000008940697}
m_Center: {x: 0.00000006274173, y: 7.0164957, z: -0.0000000941126}
--- !u!23 &1386843749
MeshRenderer:
m_ObjectHideFlags: 0
@@ -5156,6 +5009,68 @@ Transform:
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 50, y: -30, z: 0}
--- !u!1001 &9074197596846296259
PrefabInstance:
m_ObjectHideFlags: 0
serializedVersion: 2
m_Modification:
serializedVersion: 3
m_TransformParent: {fileID: 0}
m_Modifications:
- target: {fileID: 4463416604269783915, guid: 4c53ddc4a767a214e853d6ef8e5841a1, type: 3}
propertyPath: m_Name
value: Sphere
objectReference: {fileID: 0}
- target: {fileID: 5999486385027004331, guid: 4c53ddc4a767a214e853d6ef8e5841a1, type: 3}
propertyPath: m_LocalPosition.x
value: -13.138
objectReference: {fileID: 0}
- target: {fileID: 5999486385027004331, guid: 4c53ddc4a767a214e853d6ef8e5841a1, type: 3}
propertyPath: m_LocalPosition.y
value: -2.91
objectReference: {fileID: 0}
- target: {fileID: 5999486385027004331, guid: 4c53ddc4a767a214e853d6ef8e5841a1, type: 3}
propertyPath: m_LocalPosition.z
value: -0.63
objectReference: {fileID: 0}
- target: {fileID: 5999486385027004331, guid: 4c53ddc4a767a214e853d6ef8e5841a1, type: 3}
propertyPath: m_LocalRotation.w
value: 1
objectReference: {fileID: 0}
- target: {fileID: 5999486385027004331, guid: 4c53ddc4a767a214e853d6ef8e5841a1, type: 3}
propertyPath: m_LocalRotation.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 5999486385027004331, guid: 4c53ddc4a767a214e853d6ef8e5841a1, type: 3}
propertyPath: m_LocalRotation.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 5999486385027004331, guid: 4c53ddc4a767a214e853d6ef8e5841a1, type: 3}
propertyPath: m_LocalRotation.z
value: 0
objectReference: {fileID: 0}
- target: {fileID: 5999486385027004331, guid: 4c53ddc4a767a214e853d6ef8e5841a1, type: 3}
propertyPath: m_LocalEulerAnglesHint.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 5999486385027004331, guid: 4c53ddc4a767a214e853d6ef8e5841a1, type: 3}
propertyPath: m_LocalEulerAnglesHint.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 5999486385027004331, guid: 4c53ddc4a767a214e853d6ef8e5841a1, type: 3}
propertyPath: m_LocalEulerAnglesHint.z
value: 0
objectReference: {fileID: 0}
m_RemovedComponents:
- {fileID: 8982888368890146633, guid: 4c53ddc4a767a214e853d6ef8e5841a1, type: 3}
- {fileID: 1594777189990218189, guid: 4c53ddc4a767a214e853d6ef8e5841a1, type: 3}
m_RemovedGameObjects: []
m_AddedGameObjects: []
m_AddedComponents:
- targetCorrespondingSourceObject: {fileID: 4463416604269783915, guid: 4c53ddc4a767a214e853d6ef8e5841a1, type: 3}
insertIndex: -1
addedObject: {fileID: 727261133}
m_SourcePrefab: {fileID: 100100000, guid: 4c53ddc4a767a214e853d6ef8e5841a1, type: 3}
--- !u!1660057539 &9223372036854775807
SceneRoots:
m_ObjectHideFlags: 0
@@ -5199,7 +5114,7 @@ SceneRoots:
- {fileID: 1280898516}
- {fileID: 1092836727}
- {fileID: 1169290264}
- {fileID: 727261130}
- {fileID: 9074197596846296259}
- {fileID: 6503154}
- {fileID: 1386843751}
- {fileID: 1704821284}

View File

@@ -2,6 +2,7 @@ using System;
using UnityEngine;
using UnityEngine.SceneManagement;
using TMPro;
using GeoSus.Client;
public class FlappyBirdAllInOne : MonoBehaviour, ITask
{
@@ -35,7 +36,7 @@ public class FlappyBirdAllInOne : MonoBehaviour, ITask
public string TaskID { get; set; }
public TaskType TaskType { get; set; }
public string TaskName { get; set; }
public (double, double) TaskLocation { get; set; }
public Position TaskLocation { get; set; }
public bool IsCompleted { get; private set; }

View File

@@ -1,46 +1,55 @@
using UnityEngine;
[RequireComponent(typeof(Rigidbody2D))]
[RequireComponent(typeof(Collider2D))]
[RequireComponent(typeof(Rigidbody))]
[RequireComponent(typeof(Collider))]
public class DraggableObject : MonoBehaviour
{
[Header("Přetahování")]
public float dragSmoothness = 15f;
[Header("Vizuální zpětná vazba")]
public SpriteRenderer spriteRenderer;
public Renderer Renderer;
public Color normalColor = Color.white;
public Color dragColor = new Color(1f, 1f, 0.5f);
public float scaleOnDrag = 1.15f;
private Rigidbody2D rb;
private Rigidbody rb;
private Camera mainCamera;
private bool isDragging = false;
private Vector3 targetPosition;
private Vector3 originalScale;
private bool hasBeenScored = false;
private Plane _dragPlane;
private Collider col;
void Awake()
{
rb = GetComponent<Rigidbody2D>();
rb = GetComponent<Rigidbody>();
col = GetComponent<Collider>();
mainCamera = Camera.main;
originalScale = transform.localScale;
if (spriteRenderer == null)
spriteRenderer = GetComponent<SpriteRenderer>();
if (Renderer == null)
Renderer = GetComponent<Renderer>();
}
void Start()
{
rb.gravityScale = 0f;
rb.constraints = RigidbodyConstraints2D.FreezeRotation;
rb.useGravity = false;
rb.constraints = RigidbodyConstraints.FreezeRotation;
targetPosition = transform.position;
}
void Update()
{
HandleInput();
}
void FixedUpdate()
{
if (isDragging)
transform.position = Vector3.Lerp(transform.position, targetPosition, Time.deltaTime * dragSmoothness);
{
Vector3 newPos = Vector3.Lerp(rb.position, targetPosition, Time.fixedDeltaTime * dragSmoothness);
rb.MovePosition(newPos);
}
}
void HandleInput()
@@ -48,13 +57,13 @@ public class DraggableObject : MonoBehaviour
if (Input.touchCount > 0)
{
Touch touch = Input.GetTouch(0);
Vector3 worldPos = mainCamera.ScreenToWorldPoint(new Vector3(touch.position.x, touch.position.y, 10f));
//Vector3 worldPos = mainCamera.ScreenToWorldPoint(new Vector3(touch.position.x, touch.position.y, 10f));
if (touch.phase == TouchPhase.Began)
TryStartDrag(worldPos);
TryStartDrag(touch.position);
else if (touch.phase == TouchPhase.Moved || touch.phase == TouchPhase.Stationary)
{
if (isDragging) targetPosition = worldPos;
if (isDragging) UpdateDrag(touch.position);
}
else if (touch.phase == TouchPhase.Ended || touch.phase == TouchPhase.Canceled)
{
@@ -63,49 +72,70 @@ public class DraggableObject : MonoBehaviour
}
else
{
Vector3 worldPos = mainCamera.ScreenToWorldPoint(new Vector3(Input.mousePosition.x, Input.mousePosition.y, 10f));
//Vector3 worldPos = mainCamera.ScreenToWorldPoint(new Vector3(Input.mousePosition.x, Input.mousePosition.y, 10f));
if (Input.GetMouseButtonDown(0))
TryStartDrag(worldPos);
TryStartDrag(Input.mousePosition);
else if (Input.GetMouseButton(0) && isDragging)
targetPosition = worldPos;
UpdateDrag(Input.mousePosition);
else if (Input.GetMouseButtonUp(0) && isDragging)
EndDrag();
}
}
void TryStartDrag(Vector3 worldPos)
void TryStartDrag(Vector3 screenPos)
{
if (GetComponent<Collider2D>().OverlapPoint(worldPos))
StartDrag(worldPos);
}
Debug.Log("Trying to start drag at: " + screenPos);
Ray ray = mainCamera.ScreenPointToRay(screenPos);
RaycastHit hit;
void StartDrag(Vector3 worldPos)
{
isDragging = true;
rb.linearVelocity = Vector2.zero;
targetPosition = worldPos;
transform.localScale = originalScale * scaleOnDrag;
if (spriteRenderer != null)
if (Physics.Raycast(ray, out hit))
{
spriteRenderer.color = dragColor;
spriteRenderer.sortingOrder = 10;
if (hit.collider == col)
{
StartDrag(hit.point);
}
}
}
void StartDrag(Vector3 hitPoint)
{
Debug.Log("Started dragging at: " + hitPoint);
isDragging = true;
rb.isKinematic = false;
rb.linearVelocity = Vector3.zero;
_dragPlane = new Plane(-mainCamera.transform.forward, hitPoint);
targetPosition = hitPoint;
transform.localScale = originalScale * scaleOnDrag;
if (Renderer != null)
{
Renderer.material.color = dragColor;
}
}
void UpdateDrag(Vector3 screenPos)
{
Ray ray = mainCamera.ScreenPointToRay(screenPos);
float distance;
if (_dragPlane.Raycast(ray, out distance))
{
targetPosition = ray.GetPoint(distance);
}
}
void EndDrag()
{
Debug.Log("Ended dragging");
isDragging = false;
rb.isKinematic = true;
transform.localScale = originalScale;
if (spriteRenderer != null)
if (Renderer != null)
{
spriteRenderer.color = normalColor;
spriteRenderer.sortingOrder = 0;
Renderer.material.color = normalColor;
}
}
public void OnScored()
{
Debug.Log("Object scored");
if (hasBeenScored) return;
hasBeenScored = true;
isDragging = false;
@@ -123,8 +153,8 @@ public class DraggableObject : MonoBehaviour
elapsed += Time.deltaTime;
float t = elapsed / duration;
transform.localScale = Vector3.Lerp(startScale, Vector3.zero, t);
if (spriteRenderer != null)
spriteRenderer.color = new Color(normalColor.r, normalColor.g, normalColor.b, 1f - t);
if (Renderer != null)
Renderer.material.color = new Color(normalColor.r, normalColor.g, normalColor.b, 1f - t);
yield return null;
}

View File

@@ -1,6 +1,7 @@
using UnityEngine;
using System.Collections;
using UnityEngine;
[RequireComponent(typeof(CapsuleCollider))]
public class Hole : MonoBehaviour
{
[Header("Nastavení")]
@@ -10,13 +11,9 @@ public class Hole : MonoBehaviour
[Tooltip("Síla vtahování itemu k díře")]
public float pullForce = 4f;
[Header("Pohyb díry (volitelné)")]
public bool hasMovement = false;
public float moveSpeed = 2f;
public Vector2 moveRange = new Vector2(1.5f, 0f);
[Header("Vizuál")]
public SpriteRenderer glowRenderer;
public Renderer glowRenderer;
private Vector3 startPosition;
private bool isGlowing = false;
@@ -35,27 +32,24 @@ public class Hole : MonoBehaviour
void Update()
{
if (hasMovement)
{
float x = startPosition.x + Mathf.Sin(Time.time * moveSpeed) * moveRange.x;
float y = startPosition.y + Mathf.Cos(Time.time * moveSpeed * 0.7f) * moveRange.y;
transform.position = new Vector3(x, y, transform.position.z);
}
}
void OnTriggerStay2D(Collider2D other)
void OnTriggerStay(Collider other)
{
Debug.Log($"Trigger stay with: {other.gameObject.name}");
DraggableObject draggable = other.GetComponent<DraggableObject>();
if (draggable == null) return;
float dist = Vector2.Distance(transform.position, other.transform.position);
Debug.Log($"Draggable object detected: {other.gameObject.name}");
float dist = Vector3.Distance(transform.position, other.transform.position);
Rigidbody2D rb = other.GetComponent<Rigidbody2D>();
Rigidbody rb = other.GetComponent<Rigidbody>();
if (rb != null)
{
Vector2 dir = ((Vector2)transform.position - rb.position).normalized;
rb.AddForce(dir * pullForce * Time.fixedDeltaTime, ForceMode2D.Impulse);
Debug.Log($"Rigidbody detected: {other.gameObject.name}, distance: {dist}");
Vector3 dir = (transform.position - rb.position).normalized;
rb.AddForce(dir * pullForce * Time.fixedDeltaTime, ForceMode.Impulse);
}
@@ -66,18 +60,21 @@ public class Hole : MonoBehaviour
}
}
void OnTriggerEnter2D(Collider2D other)
void OnTriggerEnter(Collider other)
{
Debug.Log($"Collider entered: {other.gameObject.name}");
if (other.GetComponent<DraggableObject>() != null) SetGlow(true);
}
void OnTriggerExit2D(Collider2D other)
void OnTriggerExit(Collider other)
{
Debug.Log($"Collider exited: {other.gameObject.name}");
if (other.GetComponent<DraggableObject>() != null) SetGlow(false);
}
void SetGlow(bool active)
{
Debug.Log($"SetGlow: {active}");
isGlowing = active;
if (glowRenderer == null) return;
glowRenderer.enabled = active;
@@ -89,8 +86,8 @@ public class Hole : MonoBehaviour
while (isGlowing && glowRenderer != null)
{
float t = Mathf.PingPong(Time.time * 3f, 1f);
Color c = glowRenderer.color;
glowRenderer.color = new Color(c.r, c.g, c.b, Mathf.Lerp(0.3f, 0.9f, t));
Color c = glowRenderer.sharedMaterial.color;
glowRenderer.sharedMaterial.color = new Color(c.r, c.g, c.b, Mathf.Lerp(0.3f, 0.9f, t));
yield return null;
}
}

View File

@@ -1,7 +1,9 @@
using System;
using UnityEngine;
using UnityEngine.Events;
using GeoSus.Client;
public class LevelManager : MonoBehaviour
public class LevelManager : MonoBehaviour, ITask
{
public static LevelManager Instance;
@@ -14,11 +16,15 @@ public class LevelManager : MonoBehaviour
private int scoredCount = 0;
void Awake()
{
if (Instance == null) Instance = this;
else Destroy(gameObject);
}
public string TaskID { get; set; }
public TaskType TaskType { get; set; }
public string TaskName { get; set; }
public Position TaskLocation { get; set; }
public bool IsCompleted { get; private set; } = false;
protected Action<ITask> OnCompleted;
public void RegisterItem()
{
@@ -38,4 +44,25 @@ public class LevelManager : MonoBehaviour
public int GetScoredCount() => scoredCount;
public int GetTotalCount() => itemsToScore;
public void Initialize(Action<ITask> onCompleted)
{
OnCompleted = onCompleted;
IsCompleted = false;
ResetCounter();
}
public void ExitTask(Action<ITask> onExit)
{
onExit?.Invoke(this);
}
public void Complete()
{
if (IsCompleted) return;
IsCompleted = true;
OnCompleted?.Invoke(this);
}
}

View File

@@ -7,10 +7,11 @@ using System.Collections.Generic;
public class ObjectSpawner : MonoBehaviour
{
public static ObjectSpawner Instance;
[SerializeField]
private List<GameObject> spawnedHoles = new List<GameObject>();
[Header("Prefaby")]
public GameObject[] objectPrefabs;
public GameObject holePrefab;
[Header("Počty")]
[Tooltip("Kolik předmětů spawnovat")]
@@ -28,12 +29,9 @@ public class ObjectSpawner : MonoBehaviour
public float minY = -5f;
public float maxY = 4f;
[Header("Rodiče pro přehlednost (volitelné)")]
public Transform objectParent;
public Transform holeParent;
private List<GameObject> spawnedObjects = new List<GameObject>();
private List<GameObject> spawnedHoles = new List<GameObject>();
void Awake()
{
@@ -57,41 +55,42 @@ public class ObjectSpawner : MonoBehaviour
LevelManager.Instance.ResetCounter();
}
SpawnHoles();
//SpawnHoles();
SpawnObjects();
}
void SpawnHoles()
{
for (int i = 0; i < holeCount; i++)
{
Vector2 pos = RandomPos(1f);
GameObject hole = Instantiate(holePrefab, pos, Quaternion.identity, holeParent);
Hole h = hole.GetComponent<Hole>();
if (h != null && holesMove)
{
h.hasMovement = true;
h.moveSpeed = holeMoveSpeed;
h.moveRange = new Vector2(Random.Range(0.8f, 1.8f), 0f);
}
spawnedHoles.Add(hole);
}
}
void SpawnObjects()
//void SpawnHoles()
//{
// for (int i = 0; i < holeCount; i++)
// {
// Vector2 pos = RandomPos(1f);
// GameObject hole = Instantiate(holePrefab, pos, Quaternion.identity, holeParent);
// Hole h = hole.GetComponent<Hole>();
// if (h != null && holesMove)
// {
// h.hasMovement = true;
// h.moveSpeed = holeMoveSpeed;
// h.moveRange = new Vector2(Random.Range(0.8f, 1.8f), 0f);
// }
// spawnedHoles.Add(hole);
// }
//}
public void SpawnObjects()
{
Debug.Log($"Spawning {objectCount} objects...");
for (int i = 0; i < objectCount; i++)
{
GameObject prefab = objectPrefabs[Random.Range(0, objectPrefabs.Length)];
Vector2 pos = RandomPos(0.5f);
GameObject obj = Instantiate(prefab, pos, Quaternion.identity, objectParent);
Vector3 pos = RandomPos(0.5f);
GameObject obj = Instantiate(prefab, pos, Quaternion.identity);
// Náhodná barva
SpriteRenderer sr = obj.GetComponent<SpriteRenderer>();
Renderer sr = obj.GetComponent<Renderer>();
if (sr != null)
sr.color = Random.ColorHSV(0f, 1f, 0.7f, 1f, 0.9f, 1f);
sr.sharedMaterial.color = Random.ColorHSV(0f, 1f, 0.7f, 1f, 0.9f, 1f);
spawnedObjects.Add(obj);
}
@@ -100,14 +99,17 @@ public class ObjectSpawner : MonoBehaviour
public void Clear()
{
foreach (var o in spawnedObjects) if (o != null) Destroy(o);
foreach (var h in spawnedHoles) if (h != null) Destroy(h);
spawnedObjects.Clear();
//foreach (var h in spawnedHoles) if (h != null) Destroy(h);
//spawnedObjects.Clear();
spawnedHoles.Clear();
}
Vector2 RandomPos(float margin) =>
new Vector2(
Vector3 RandomPos(float margin)
{
return new Vector3(
Random.Range(minX + margin, maxX - margin),
Random.Range(minY + margin, maxY - margin)
Random.Range(minY + margin, maxY - margin),
0.5f
);
}
}

View File

@@ -3,6 +3,7 @@ using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
using GeoSus.Client;
public class DraggableKey : MonoBehaviour,
IBeginDragHandler, IDragHandler, IEndDragHandler, ITask
@@ -27,7 +28,7 @@ public class DraggableKey : MonoBehaviour,
public string TaskID { get; set; }
public TaskType TaskType { get; set; }
public string TaskName { get; set; }
public (double, double) TaskLocation { get; set; }
public Position TaskLocation { get; set; }
public bool IsCompleted { get; private set; }
private Action<ITask> _onCompleted;

View File

@@ -3,6 +3,7 @@ using System.Collections;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;
using GeoSus.Client;
public class CableMiniGame : MonoBehaviour, ITask
{
@@ -21,7 +22,7 @@ public class CableMiniGame : MonoBehaviour, ITask
public string TaskID { get; set; }
public TaskType TaskType { get; set; }
public string TaskName { get; set; }
public (double, double) TaskLocation { get; set; }
public Position TaskLocation { get; set; }
public bool IsCompleted { get; private set; }
private Action<ITask> _onCompleted;

View File

@@ -2,7 +2,7 @@ using System;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
using GeoSus.Client;
public class WindController : MonoBehaviour, ITask
{
@@ -24,7 +24,7 @@ public class WindController : MonoBehaviour, ITask
public string TaskID { get; set; }
public TaskType TaskType { get; set; }
public string TaskName { get; set; }
public (double, double) TaskLocation { get; set; }
public Position TaskLocation { get; set; }
public bool IsCompleted { get; private set; }
void Start()

View File

@@ -78,7 +78,7 @@ Material:
- _UVSec: 0
- _ZWrite: 1
m_Colors:
- _Color: {r: 1, g: 1, b: 1, a: 1}
- _Color: {r: 0.12311344, g: 0.96881944, b: 0.7101699, a: 1}
- _EmissionColor: {r: 0, g: 0, b: 0, a: 1}
m_BuildTextureStacks: []
m_AllowLocking: 1

View File

@@ -0,0 +1,161 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1 &4463416604269783915
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 5999486385027004331}
- component: {fileID: 3096372749762576219}
- component: {fileID: 1253701241776310731}
- component: {fileID: 7907572649200639552}
- component: {fileID: 4926246710868963563}
- component: {fileID: 4731565560732594675}
m_Layer: 0
m_Name: Sphere
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &5999486385027004331
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 4463416604269783915}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: -13.138, y: -2.91, z: -0.63}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!33 &3096372749762576219
MeshFilter:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 4463416604269783915}
m_Mesh: {fileID: 10207, guid: 0000000000000000e000000000000000, type: 0}
--- !u!23 &1253701241776310731
MeshRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 4463416604269783915}
m_Enabled: 1
m_CastShadows: 1
m_ReceiveShadows: 1
m_DynamicOccludee: 1
m_StaticShadowCaster: 0
m_MotionVectors: 1
m_LightProbeUsage: 1
m_ReflectionProbeUsage: 1
m_RayTracingMode: 2
m_RayTraceProcedural: 0
m_RayTracingAccelStructBuildFlagsOverride: 0
m_RayTracingAccelStructBuildFlags: 1
m_SmallMeshCulling: 1
m_ForceMeshLod: -1
m_MeshLodSelectionBias: 0
m_RenderingLayerMask: 1
m_RendererPriority: 0
m_Materials:
- {fileID: 2100000, guid: 1a424fd36745ea14682570c13681319e, type: 2}
m_StaticBatchInfo:
firstSubMesh: 0
subMeshCount: 0
m_StaticBatchRoot: {fileID: 0}
m_ProbeAnchor: {fileID: 0}
m_LightProbeVolumeOverride: {fileID: 0}
m_ScaleInLightmap: 1
m_ReceiveGI: 1
m_PreserveUVs: 0
m_IgnoreNormalsForChartDetection: 0
m_ImportantGI: 0
m_StitchLightmapSeams: 1
m_SelectedEditorRenderState: 3
m_MinimumChartSize: 4
m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0}
m_GlobalIlluminationMeshLod: 0
m_SortingLayerID: 0
m_SortingLayer: 0
m_SortingOrder: 0
m_AdditionalVertexStreams: {fileID: 0}
--- !u!114 &7907572649200639552
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 4463416604269783915}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: fb5157d7cd78450439c40cd6f5afe6ac, type: 3}
m_Name:
m_EditorClassIdentifier: Assembly-CSharp::DraggableObject
dragSmoothness: 15
Renderer: {fileID: 0}
normalColor: {r: 1, g: 1, b: 1, a: 1}
dragColor: {r: 1, g: 1, b: 0.5, a: 1}
scaleOnDrag: 1.15
--- !u!54 &4926246710868963563
Rigidbody:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 4463416604269783915}
serializedVersion: 5
m_Mass: 1
m_LinearDamping: 0
m_AngularDamping: 0.05
m_CenterOfMass: {x: 0, y: 0, z: 0}
m_InertiaTensor: {x: 1, y: 1, z: 1}
m_InertiaRotation: {x: 0, y: 0, z: 0, w: 1}
m_IncludeLayers:
serializedVersion: 2
m_Bits: 0
m_ExcludeLayers:
serializedVersion: 2
m_Bits: 0
m_ImplicitCom: 1
m_ImplicitTensor: 1
m_UseGravity: 1
m_IsKinematic: 0
m_Interpolate: 0
m_Constraints: 0
m_CollisionDetection: 0
--- !u!136 &4731565560732594675
CapsuleCollider:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 4463416604269783915}
m_Material: {fileID: 0}
m_IncludeLayers:
serializedVersion: 2
m_Bits: 0
m_ExcludeLayers:
serializedVersion: 2
m_Bits: 0
m_LayerOverridePriority: 0
m_IsTrigger: 0
m_ProvidesContacts: 0
m_Enabled: 1
serializedVersion: 2
m_Radius: 0.5
m_Height: 1
m_Direction: 1
m_Center: {x: 0, y: 0, z: 0}

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 4c53ddc4a767a214e853d6ef8e5841a1
PrefabImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant: