This commit is contained in:
Andras Schmelczer 2026-09-13 20:53:11 +01:00
commit 36a53f343e
38 changed files with 300431 additions and 0 deletions

View file

@ -0,0 +1,292 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Networking;
[NetworkSettings(channel = 0, sendInterval = 0)]
public class CarClient : NetworkBehaviour {
public float delay = 2;
private float timeAlive = 0;
private StateBuffer stateBuffer;
public GameObject brakeLights;
public Transform explosion;
public Transform fire;
private Transform thisFire;
private bool alreadyOnFire = false;
private bool isDead = false;
private void Start ()
{
if (isClient)
{
}
}
public void FixedUpdate ()
{
if(isClient)
{
if(stateBuffer == null)
stateBuffer = new StateBuffer(delay);
if (stateBuffer.CanStartInterpolating())
{
//Pontosabb, mint a Time.time
timeAlive += Time.deltaTime;
stateBuffer.Interpolate(timeAlive);
if (isDead)
{
transform.position = Vector3.zero;
return;
}
ChangeBrakeLights(stateBuffer.IsBraking());
if(stateBuffer.ShouldExplode() && !alreadyOnFire)
{
thisFire = Instantiate(fire, transform.position, transform.rotation);
alreadyOnFire = true;
}
if (stateBuffer.ShouldDie())
{
Instantiate(explosion, transform.position, transform.rotation);
Destroy(thisFire.gameObject);
isDead = true;
}
transform.position = stateBuffer.GetCurrentPosition();
transform.rotation = stateBuffer.GetCurrentRotation();
}
}
}
//A féklámpa ki/be kapcsolására
private void ChangeBrakeLights (bool newState)
{
for (int a = 0; a < brakeLights.transform.childCount; a++)
brakeLights.transform.GetChild(a).gameObject.SetActive(newState);
}
//RPC: Remote procedure call, QoS: reliable sequenced
[ClientRpc(channel = 0)]
public void RpcDecodeBits (ulong newStateBits)
{
CompressedStateFrame compressedStateFrame = new CompressedStateFrame(newStateBits);
StateFrame newStateFrame = compressedStateFrame.GetDecodedStateFrame(transform);
stateBuffer.AddFrame(ref newStateFrame);
}
}
public class CompressedStateFrame
{
private ulong stateBits;
public CompressedStateFrame(ulong stateBits)
{
this.stateBits = stateBits;
}
public StateFrame GetDecodedStateFrame(Transform carTransform)
{
StateFrame decodedFrame = new StateFrame();
decodedFrame.isBraking = TakeAwayLastBitAsBool();
float xPosition = TakeAwayLastNbitsAsFixed(12, 1f / 16);
float zPosition = TakeAwayLastNbitsAsFixed(12, 1f / 16);
decodedFrame.position = new Vector3(xPosition, carTransform.position.y, zPosition);
float rotation = TakeAwayLastNbitsAsFixed(7, 360f / 127);
decodedFrame.direction = Quaternion.AngleAxis(rotation, carTransform.up);
decodedFrame.shouldExplode = TakeAwayLastBitAsBool();
decodedFrame.shouldDie = TakeAwayLastBitAsBool();
decodedFrame.isTurning = TakeAwayLastBitAsBool();
decodedFrame.serverStartTime = (float)stateBits / 1000;
return decodedFrame;
}
private bool TakeAwayLastBitAsBool ()
{
bool lastBit = ( stateBits & 1 ) == 1;
stateBits >>= 1;
return lastBit;
}
private float TakeAwayLastNbitsAsFixed (int n, float multiplier)
{
return (float)TakeAwayLastNbitsAsUlong(n) * multiplier;
}
private ulong TakeAwayLastNbitsAsUlong (int n)
{
ulong mask = (ulong)( 1 << n ) - 1;
ulong lastNbits = stateBits & mask;
stateBits >>= n;
return lastNbits;
}
}
//Egy autó egy állapota, tekinthetőek keyframeeknek, amik között lineárisan készítünk kitöltő állapotokat készítünk
[System.Serializable]
public class StateFrame
{
//client mikor kezdte el?
public float clientStartTime;
//szerver mikor kezdte el?
public float serverStartTime;
//Az időket az adott autó spawnolásától mérjük.
public Vector3 position;
public Quaternion direction;
public bool isBraking;
public bool isTurning;
public bool shouldExplode;
public bool shouldDie;
}
//Az autó állapotframejei itt tárolódnak
[System.Serializable]
public class StateBuffer
{
private float timeOfLastUpdate;
private float delay;
private float intervalLengthClient;
private float intervalLengthServer;
private float qBetweenFrames;
private bool isInValidState = true;
private bool hasStarted = false;
//jelenleg ez a két frame közt van
private StateFrame before;
private StateFrame after;
//Egy autó jövője delay mp-re előre
private Queue next;
public StateBuffer (float delay)
{
this.delay = delay;
next = new Queue();
}
public void AddFrame (ref StateFrame newFrame)
{
//A visszajátszás sebessége kiszámolásáshoz fontos tudni
timeOfLastUpdate = newFrame.serverStartTime;
if (before == null)
{
before = newFrame;
before.clientStartTime = 0;
}
else if (after == null)
after = newFrame;
else
next.Enqueue(newFrame);
}
public bool CanStartInterpolating ()
{
if (before != null && after != null)
hasStarted = true;
return hasStarted;
}
public void Interpolate (float timeAlive)
{
do
{
isInValidState = true;
intervalLengthClient = timeAlive - before.clientStartTime;
intervalLengthServer = after.serverStartTime - before.serverStartTime;
CompansateIfOutOfSync();
if (NeedToChangeFrame())
{
try
{
ChangeFrame(timeAlive);
}
catch (System.Exception)
{
isInValidState = false;
return;
}
}
} while (NeedToChangeFrame());
qBetweenFrames = intervalLengthClient / intervalLengthServer;
}
private void CompansateIfOutOfSync()
{
intervalLengthServer /= ( timeOfLastUpdate - before.serverStartTime ) / delay;
}
private bool NeedToChangeFrame()
{
return intervalLengthClient > intervalLengthServer || intervalLengthServer == 0;
}
private void ChangeFrame (float timeAlive)
{
before = after;
before.clientStartTime = timeAlive - ( intervalLengthClient - intervalLengthServer );
after = (StateFrame)next.Dequeue();
}
public bool ShouldExplode()
{
return before.shouldExplode;
}
public bool ShouldDie()
{
return before.shouldDie;
}
public bool IsBraking ()
{
return before.isBraking;
}
public Vector3 GetCurrentPosition()
{
if (!isInValidState) return Vector3.zero;
if (Vector3.Magnitude(after.position - before.position) > 20)
{
Debug.Log("jump " + before.position + ", " + after.position);
return after.position;
}
if (before.isTurning)
return Vector3.Slerp(before.position, after.position, qBetweenFrames);
else
return Vector3.Lerp(before.position, after.position, qBetweenFrames);
}
public Quaternion GetCurrentRotation()
{
if (!isInValidState) return Quaternion.identity;
return Quaternion.Slerp(before.direction, after.direction, qBetweenFrames);
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 73000d69c2d7f854bbd601900f5b4c60
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,320 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Networking;
/* High-level car controller
* Az autót irányító állapotgép.
*
* Jelölések: public PascalCase : futásidejű "interface"
* public camelCase : Unityből állítható konstansok
*
* Megkötések:
* Két kanyar nem lehet közvetlen egymás mellett.
* A 10-es rétegen az útjelek, a 9-esen az autók legyenek.
*
* Az itt lévő logika nem túl bonyolult. Igazából 3 állapotgép van, ezek hierarchikusan vannak szervezve.
* Könnyen bővíthető plusz állapotokkal, úttipusokkal, közlekedési helyezetekkel.
*
* Networking az autószínen kívül nincs benne. A kód csak a szerver oldalán fut.
*
* A tájékozódáshoz raycastingot használ, ezért megfelelő trigger collidereket kell elhelyezni speciális helyeken.
* A "junctionAssets/prefabs/roads" mappában látható erre példa.
* Szükség esetén a raycasting használat kicsit leljebb szorítható, ha csak nagyon gyenge CPU-nk van.
*
* Csak akkor áll meg másik autó miatt, ha az irányvektorik 90° > szöget zárnak be.
*
* A kereszteződéseket leszámítva a pálya segítségével magától közlekedik.
* Keresztetződéshez érve a TrafficController-től kérdezi meg, hogy merre mehet.
*/
//Kizárólag az autó színét szinkronizálja
[NetworkSettings(channel = 0, sendInterval = 0.1f)]
public class CarHL : NetworkBehaviour
{
/* Controls */ //Ezeken kívűl más nem kell a futásidejű irányításához, a többit magától megoldja.
[SyncVar(hook = "changeColor")]
public Color CarColor;
//Jelenleg melyik úton van?
public int RoadID;
/**/
[Header("Sensors")]
public float carSensorLength = 7;
public float roadSensorLength = 5;
public Vector3 driverSensorOffset;
//Driver szenzor széttartás
public float deltaX = 0.2f;
//Innen indulnak a raycastok
private Vector3 driverSensor;
[Header("External references")]
public string trafficContollerName = "Traffic controller";
public Renderer body;
public CarLL carLL;
private TrafficController traffic;
[Header("Misc")]
public float turningChance = 0.5f;
//Állapotgépek
//Fő állapot
private CarState carState;
//Az úton ilyen állapotban van
private RoadCarState roadCarState;
//A kereszteződésben ilyen állapotban van
private CrossroadCarState crossroadCarState;
//A következő utak ID-je.
private int crossroadID;
private int rightRoadID;
private int forwardRoadID;
public void Start ()
{
changeColor(CarColor);
if (!isServer) return;
traffic = GameObject.Find(trafficContollerName).GetComponent<TrafficController>();
//Alapállapot
carState = CarState.road;
roadCarState = RoadCarState.onRoad;
}
public void FixedUpdate ()
{
//Ez az egész modul (a színváltást leszámítva) kliens oldalon nem csinál semmit.
if (!isServer) return;
//Innen indulnak a raycastok
driverSensor = transform.position + transform.forward * driverSensorOffset.x +
transform.right * driverSensorOffset.z + transform.up * driverSensorOffset.y;
/* A raycastok Debugban:
* piros : nem talált el semmi lényegeset
* kék : autót talált, de szembejövő, szóval ignore
* zöld : meg kell állni miatta
*/
//Itt tárolódnak a raycastok
RaycastHit hit;
switch (carState)
{
case CarState.road:
carLL.IsBraking = shouldStopOfCar();
switch (roadCarState)
{
case RoadCarState.onRoad:
if (Physics.Raycast(driverSensor, -transform.up, out hit, roadSensorLength, 1 << 10))
{
if (hit.collider)
{
Debug.DrawLine(driverSensor, hit.point, Color.green);
if (hit.collider.tag == "rightTurn")
{
carLL.StartTurning(false);
roadCarState = RoadCarState.onTurn;
}
else if (hit.collider.tag == "leftTurn")
{
carLL.StartTurning(true);
roadCarState = RoadCarState.onTurn;
}
}
}
break;
//Erre az állapotra azért van szükség, hogy csak egyszer triggerelődjön a StartTurning
case RoadCarState.onTurn:
if (carLL.IsTurningDone())
roadCarState = RoadCarState.onRoad;
break;
}
/* KÖZÖS az összes roadStateben, hogy figyel a kereszteződésre. */
//Amúgy ez a feature nincsen kihasználva, de a jövőben lehet, hogy ez megváltozik.
if (Physics.Raycast(driverSensor, transform.forward, out hit, roadSensorLength, 1 << 10))
if (hit.collider)
for (int i = 0; i < 4; i++)
if (hit.collider.tag == "crossRoad" + i)
{
carState = CarState.crossroad;
crossroadCarState = CrossroadCarState.beforeLightWaiting;
}
break;
case CarState.crossroad:
//Ez az alapértelmezett
carLL.IsBraking = true;
switch (crossroadCarState)
{
case CrossroadCarState.beforeLightWaiting:
if (Physics.Raycast(driverSensor, transform.forward, out hit, roadSensorLength, 1 << 10))
if (hit.collider)
Debug.DrawLine(driverSensor, hit.point, Color.blue);
for (int i = 0; i < 4; i++)
if (hit.collider.tag == "crossRoad" + i)
if (traffic.getInfo(i, hit.collider.name, ref forwardRoadID, ref rightRoadID))
{
crossroadCarState = CrossroadCarState.beforeLightStarting;
crossroadID = i;
traffic.trafficLights.isThereSpace[crossroadID] = false;
}
break;
case CrossroadCarState.beforeLightStarting:
crossroadCarState = CrossroadCarState.beforeLightWaiting;
if (Random.Range(0.0f, 1.0f) < turningChance)
{
if (traffic.changeRoad(RoadID, rightRoadID))
{
RoadID = rightRoadID;
crossroadCarState = CrossroadCarState.preManouverRight;
}
}
else
{
if (traffic.changeRoad(RoadID, forwardRoadID))
{
RoadID = forwardRoadID;
crossroadCarState = CrossroadCarState.preManouverForward;
}
}
if(crossroadCarState == CrossroadCarState.beforeLightWaiting)
traffic.trafficLights.isThereSpace[crossroadID] = true;
break;
//Maga az áthaladás azonos, a lényeg, hogy előtte be kell gurulni a kereszteződésbe, és csak utána
//lehet elkezdeni.
case CrossroadCarState.preManouverForward:
carLL.IsBraking = false;
if (Physics.Raycast(driverSensor, -transform.up, out hit, roadSensorLength, 1 << 10))
if (hit.collider && hit.collider.tag == "crossRoad")
crossroadCarState = CrossroadCarState.manouver;
break;
case CrossroadCarState.preManouverRight:
carLL.IsBraking = false;
if (Physics.Raycast(driverSensor, -transform.up, out hit, roadSensorLength, 1 << 10))
if (hit.collider && hit.collider.tag == "crossRoad")
{
carLL.StartTurning(false);
crossroadCarState = CrossroadCarState.manouver;
}
break;
case CrossroadCarState.manouver:
carLL.IsBraking = false;
if (Physics.Raycast(driverSensor, -transform.up, out hit, roadSensorLength, 1 << 10))
if (hit.collider && hit.collider.tag != "crossRoad")
{
carState = CarState.road;
traffic.trafficLights.isThereSpace[crossroadID] = true;
}
break;
}
break;
}
}
private void changeColor (Color newColor)
{
body.materials[1].color = newColor;
}
private bool shouldStopOfCar ()
{
RaycastHit hit;
//Az autók a 9-es rétegen vannak
//Ezt lehetne változóba tenni, de ez működik, szóval nem kell megváltoztatni.
//Ha gond a szerveroldali CPU használat, akkor ezen kell javítani.
for (int i = -3; i <= 3; i++)
{
Debug.DrawLine(driverSensor, driverSensor + ( Quaternion.Euler(0, deltaX * i, 0) * transform.forward * carSensorLength ), Color.red);
if (Physics.Raycast(driverSensor, Quaternion.Euler(0, deltaX * i, 0) * transform.forward, out hit, carSensorLength, 1 << 9, QueryTriggerInteraction.Collide))
{
Debug.DrawLine(driverSensor, hit.point, Color.blue);
float angle = Mathf.Abs(transform.eulerAngles.y - hit.collider.transform.eulerAngles.y);
if (angle >= 180) angle -= 360;
angle = Mathf.Abs(angle);
if (hit.collider.tag == "car" && angle < 90)
{
Debug.DrawLine(driverSensor, hit.point, Color.green);
return true;
}
}
}
return false;
}
//A vezető szemét jelképező gömb rajzolása
private void OnDrawGizmos ()
{
driverSensor = transform.position + transform.forward * driverSensorOffset.x +
transform.right * driverSensorOffset.z + transform.up * driverSensorOffset.y;
Gizmos.DrawSphere(driverSensor, 0.15f);
}
//Robbanás
private void OnTriggerEnter (Collider other)
{
if (!isServer) return;
if (other.tag == "car")
{
carLL.ShouldExplode = true;
if(RoadID > 0)
{
traffic.roads[RoadID].carCount--;
if(carState == CarState.crossroad)
{
traffic.roads[RoadID].isThereSpace = true;
traffic.trafficLights.isThereSpace[crossroadID] = true;
}
RoadID = -1;
}
}
}
}
public enum CarState
{
road,
crossroad
}
public enum RoadCarState
{
onRoad,
onTurn
}
public enum CrossroadCarState
{
beforeLightWaiting,
beforeLightStarting,
preManouverForward,
preManouverRight,
manouver
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a77c00a63a6d7274dafec539e3ac85b8
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a77c00a63a6d7274dafec539e3ac85b8
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f1c2c117a06720446b95a0b36486115d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,306 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Networking;
/* Traffic controller
* Felelős a forgalom irányításáért és a forgalom figyeléséért.
*
* Az autók innen kérdezik le, hogy adott kereszteződésből merre lehet menni a lámpa szerint, illetve van-e még hely
* az adott úton.
*
* Autókat spawnol (és színez is).
*
* Lekérdezi a lámpák állapotát.
* Jegyzi a várakozó autók számát és elüldi a megfelelő szervernek.
*
* Csak szerveroldalon fut.
*/
//Nem kell SyncVar-ral foglalkoznia a UNET-nek
[NetworkSettings(channel = 0, sendInterval = 0)]
public class TrafficController : NetworkBehaviour {
[Header("Network setup")]
public string url = "http://localhost:8080/";
public string getURI = "/trafficLights/";
public string putRoadsURI = "/roads/";
public string putCarCountURI = "/passedCarCount";
[Header("Road setup")]
public Road[] roads = new Road[16];
public List<CrossroadUnity> crossroadsUnity;
public TrafficLights trafficLights;
[Header("Misc setup")]
public GameObject carPrefab;
public List<Color> carColors;
public carCount carCounter;
//ezek roadID-k
public int[] spawnPosition = { 0, 1, 5, 6, 10, 11, 15, 12 };
public float updateInterval = 1;
public float spawnInterval = 0.5f;
private float timePassedUpdate = 0;
private float timePassedSpawn = 0;
private int current = 0;
void Start () {
if (!isServer) return;
trafficLights = new TrafficLights();
StartCoroutine("getState");
}
void Update () {
if (!isServer) return;
timePassedUpdate += Time.deltaTime;
if (timePassedUpdate >= updateInterval)
{
StartCoroutine("getState");
StartCoroutine("putRoads");
StartCoroutine("putCarCount");
timePassedUpdate = 0;
}
timePassedSpawn += Time.deltaTime;
if (timePassedSpawn >= spawnInterval)
{
spawnCar(spawnPosition[current++]);
current %= spawnPosition.Length;
timePassedSpawn = 0;
}
}
IEnumerator getState ()
{
using (WWW www = new WWW(url + getURI))
{
yield return www;
trafficLights.roadSystem = JsonUtility.FromJson<RoadSystem>(www.text);
for (int i = 0; i < 4; i++)
{
crossroadsUnity[i].colors.RpcUpdateColors(trafficLights[i].N, trafficLights[i].E, trafficLights[i].S, trafficLights[i].W);
trafficLights.isWithoutLights[i] = !(trafficLights[i].N != "off" && trafficLights[i].E != "off" && trafficLights[i].S != "off" && trafficLights[i].W != "off");
}
}
}
IEnumerator putRoads ()
{
string roadStates = "{ \"NW\": { \"W\":" + roads[0].carCount.ToString() +
",\"N\":" + roads[1].carCount.ToString() +
",\"E\":" + roads[2].carCount.ToString() +
",\"S\":" + roads[3].carCount.ToString() +
"}" +
", \"NE\": { \"W\":" + roads[4].carCount.ToString() +
",\"N\":" + roads[5].carCount.ToString() +
",\"E\":" + roads[6].carCount.ToString() +
",\"S\":" + roads[7].carCount.ToString() +
"}" +
", \"SE\": { \"W\":" + roads[8].carCount.ToString() +
",\"N\":" + roads[9].carCount.ToString() +
",\"E\":" + roads[10].carCount.ToString() +
",\"S\":" + roads[11].carCount.ToString() +
"}" +
", \"SW\": { \"W\":" + roads[12].carCount.ToString() +
",\"N\":" + roads[13].carCount.ToString() +
",\"E\":" + roads[14].carCount.ToString() +
",\"S\":" + roads[15].carCount.ToString() +
"}" +
"}";
byte[] data = System.Text.Encoding.UTF8.GetBytes(roadStates);
UnityWebRequest www = UnityWebRequest.Put(url + putRoadsURI, data);
yield return www.SendWebRequest();
if (www.isNetworkError || www.isHttpError)
{
Debug.Log(www.error);
}
}
IEnumerator putCarCount ()
{
byte[] data = System.Text.Encoding.UTF8.GetBytes(carCounter.throughput.ToString());
UnityWebRequest www = UnityWebRequest.Put(url + putCarCountURI, data);
yield return www.SendWebRequest();
if (www.isNetworkError || www.isHttpError)
{
Debug.Log(www.error);
}
}
//Beállítja a hívó autó next roadId-jét és megmondja, mehet-e a lámpa alapján.
public bool getInfo(int index, string direction, ref int forwardRoadID, ref int rightRoadID)
{
bool canGo = trafficLights.canGo(index);
switch (direction)
{
case "North":
forwardRoadID = crossroadsUnity[index].southNextRoadID;
rightRoadID = crossroadsUnity[index].westNextRoadID;
return trafficLights[index].N == "green" || canGo;
case "East":
forwardRoadID = crossroadsUnity[index].westNextRoadID;
rightRoadID = crossroadsUnity[index].northNextRoadID;
return trafficLights[index].E == "green" || canGo;
case "South":
forwardRoadID = crossroadsUnity[index].northNextRoadID;
rightRoadID = crossroadsUnity[index].eastNextRoadID;
return trafficLights[index].S == "green" || canGo;
case "West":
forwardRoadID = crossroadsUnity[index].eastNextRoadID;
rightRoadID = crossroadsUnity[index].southNextRoadID;
return trafficLights[index].W == "green" || canGo;
default:
return false;
}
}
//Megmondja, hogy az autó mehet-e az alapján, hogy hány autó van a cél úton.
//Az autó arra megy majd, amerre tud.
public bool changeRoad(int current, int next)
{
if (next == -1)
{
roads[current].carCount--;
return true;
}
if (roads[next].isThereSpace)
{
roads[current].carCount--;
roads[next].carCount++;
roads[next].isThereSpace = false;
return true;
}
else return false;
}
//Szerveroldalról autót spawnol, ha tud.
[Server]
public void spawnCar(int index)
{
if(roads[index].spawnPoint)
{
if (roads[index].isThereSpace)
{
roads[index].isThereSpace = false;
roads[index].carCount++;
GameObject car = (GameObject)Instantiate(carPrefab, roads[index].spawnPoint.transform.position, roads[index].spawnPoint.transform.rotation);
car.GetComponent<CarHL>().RoadID = index;
car.GetComponent<CarHL>().CarColor = carColors[Random.Range(0, carColors.Count)];
NetworkServer.Spawn(car);
}
}
}
}
[System.Serializable]
public struct CrossroadUnity
{
public trafficLightColor colors;
//int?-et nem támogat unity, invalid érték: -1
//Itt a kereszteződésből kivezető út ID-jét tároljuk.
public int northNextRoadID;
public int southNextRoadID;
public int westNextRoadID;
public int eastNextRoadID;
}
/* Utak kiosztása:
* Egy út lehet, hogy fizikailag egy, viszont az alapján van szétosztva, hogy melyik kereszteződésbe torkollik.
* Lehet intuitívabb elnevezése lenne road helyett a lane.
*
* 1 5
* V V
* 0 > < 2 4 > < 6
* ^ ^
* 3 7
*
* 13 9
* V V
* 12 > < 14 8 > < 10
* ^ ^
* 15 11
*
* (Észak felfele van [arra mutat a fehér nyíl pályán], a 4 csomópont a 4 kereszteződésnek felel meg.)
*/
//Unityben egy utat egy ilyen szimbolizál
[System.Serializable]
public struct Road
{
//Nem kötelező megadni
public Transform spawnPoint;
//A safespace állítja
public bool isThereSpace;
public int carCount;
}
[System.Serializable]
public struct Crossroads
{
public string N;
public string E;
public string S;
public string W;
}
[System.Serializable]
public struct RoadSystem
{
public Crossroads NW;
public Crossroads NE;
public Crossroads SE;
public Crossroads SW;
}
public class TrafficLights
{
public RoadSystem roadSystem;
public bool[] isWithoutLights = { true, true, true, true };
public bool[] isThereSpace = { true, true, true, true };
public bool canGo(int i)
{
return isWithoutLights[i] && isThereSpace[i];
}
public Crossroads this[int i]
{
get {
switch(i)
{
case 0:
return roadSystem.NW;
case 1:
return roadSystem.NE;
case 2:
return roadSystem.SE;
default:
return roadSystem.SW;
}
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: acb679dffbe9eb24db245240e4cb2425
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

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

View file

@ -0,0 +1,2 @@
# Menu scriptek
Rendkívűl minimális logika, ami a menüben lévő lépkedést vezérli.

View file

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 467a2d65463222d4db9cc222c1dabaed
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,40 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/* Itt lehet állítani, hogy melyik build legyen szerver és melyik kliens.
* WebGL szerver nem lehet.
*/
public class canvasChooser : MonoBehaviour {
public GameObject serverStartCanvas;
public GameObject clientStartCanvas;
public GameObject mainCamera;
// Use this for initialization
void Start () {
#if UNITY_STANDALONE
serverStartCanvas.SetActive(false);
clientStartCanvas.SetActive(true);
mainCamera.SetActive(true);
/*serverStartCanvas.SetActive(true);
clientStartCanvas.SetActive(false);
mainCamera.SetActive(false);*/
#endif
#if UNITY_WEBGL
serverStartCanvas.SetActive(false);
clientStartCanvas.SetActive(true);
mainCamera.SetActive(true);
#endif
#if UNITY_EDITOR
serverStartCanvas.SetActive(false);
clientStartCanvas.SetActive(true);
mainCamera.SetActive(true);
#endif
}
}

View file

@ -0,0 +1,40 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/* Itt lehet állítani, hogy melyik build legyen szerver és melyik kliens.
* WebGL szerver nem lehet.
*/
public class canvasChooser : MonoBehaviour {
public GameObject serverStartCanvas;
public GameObject clientStartCanvas;
public GameObject mainCamera;
// Use this for initialization
void Start () {
#if UNITY_STANDALONE
serverStartCanvas.SetActive(false);
clientStartCanvas.SetActive(true);
mainCamera.SetActive(true);
/*serverStartCanvas.SetActive(true);
clientStartCanvas.SetActive(false);
mainCamera.SetActive(false);*/
#endif
#if UNITY_WEBGL
serverStartCanvas.SetActive(false);
clientStartCanvas.SetActive(true);
mainCamera.SetActive(true);
#endif
#if UNITY_EDITOR
serverStartCanvas.SetActive(false);
clientStartCanvas.SetActive(true);
mainCamera.SetActive(true);
#endif
}
}

View file

@ -0,0 +1,170 @@
using System.Collections;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.UI;
[NetworkSettings(channel = 0, sendInterval = 1)]
public class clientGameMenu : NetworkBehaviour {
public GameObject next;
public NetworkManager network;
private TweetAnimator tweetAnimator;
public TweetContainer tweetContainer;
//Letölti az új tweetet
private void Update ()
{
if(isClient)
{
if (tweetAnimator == null)
tweetAnimator = new TweetAnimator(0.5f, 7.5f, tweetContainer);
tweetAnimator.Animate(Time.deltaTime);
}
}
[ClientRpc(channel = 0)]
public void RpcSetNewTweet (Tweet newTweet)
{
tweetAnimator.SetNewTweet(newTweet);
}
/*public void goBack()
{
network.StopClient();
gameObject.SetActive(false);
next.SetActive(true);
next.GetComponent<clientMenu>().errorField.text = "No errors so far.";
}*/
}
[System.Serializable]
public struct Tweet
{
public TweetAccount account;
public string message;
}
[System.Serializable]
public struct TweetAccount
{
public string realName;
public string name;
public int picID;
}
[System.Serializable]
public struct TweetContainer
{
public GameObject tweetBox;
public Text realName;
public Text message;
public RawImage image;
public List<Texture> pics;
}
//Betölti az új tweetet
public class TweetAnimator
{
private Tweet tweet;
private TweetContainer tweetContainer;
private float elapsedTime;
private float animationTimeout;
private float tweetTimeout;
private bool hasNewTweet = false;
public float opacity = 0.95f;
private enum FadeState
{
notVisible,
fadingIn,
fullyVisible,
fadingOut
}
FadeState fadeState = FadeState.notVisible;
public TweetAnimator (float animationTimeout, float tweetTimeout, TweetContainer tweetContainer)
{
this.animationTimeout = animationTimeout;
this.tweetTimeout = tweetTimeout;
this.tweetContainer = tweetContainer;
}
public void Animate (float deltaTime)
{
elapsedTime += deltaTime;
float q = elapsedTime / animationTimeout;
if (q > 1)
q = 1;
switch (fadeState)
{
case FadeState.notVisible:
tweetContainer.tweetBox.GetComponent<CanvasGroup>().alpha = 0;
if (hasNewTweet)
{
elapsedTime = 0;
changeText();
hasNewTweet = false;
fadeState = FadeState.fadingIn;
}
break;
case FadeState.fadingIn:
tweetContainer.tweetBox.GetComponent<CanvasGroup>().alpha = q * opacity;
if (q >= 1)
{
elapsedTime = 0;
fadeState = FadeState.fullyVisible;
}
break;
case FadeState.fullyVisible:
if (elapsedTime > tweetTimeout)
{
elapsedTime = 0;
fadeState = FadeState.fadingOut;
}
break;
case FadeState.fadingOut:
tweetContainer.tweetBox.GetComponent<CanvasGroup>().alpha = (1 - q) * opacity;
if (q >= 1)
fadeState = FadeState.notVisible;
break;
}
}
public void SetNewTweet(Tweet newTweet)
{
tweet = newTweet;
if (fadeState == FadeState.fadingIn || fadeState == FadeState.fullyVisible)
fadeState = FadeState.fadingOut;
hasNewTweet = true;
}
private void changeText()
{
tweetContainer.realName.text = tweet.account.realName + " <color=blue>@" + tweet.account.name + "</color>";
string formattedMessage = Regex.Replace(tweet.message, "#[a-zA-Z]+", LocalReplaceMatchCase);
tweetContainer.message.text = formattedMessage;
tweetContainer.image.texture = tweetContainer.pics[tweet.account.picID];
}
private string LocalReplaceMatchCase (Match matchExpression)
{
return "<color=blue>" + matchExpression.Value + "</color>";
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2c9ca4ea21ecfa14fb07a297a985e20a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,24 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.UI;
[NetworkSettings(channel = 0, sendInterval = 0)]
public class clientMenu : NetworkBehaviour
{
public TrafficController traffic;
public Text errorField;
public GameObject next;
public bool hasStarted = false;
public NetworkManager network;
public void joinServer (string address)
{
network.networkAddress = address;
network.StartClient();
errorField.text = "Connecting...";
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 83204df2ae6cf744fb64b01124d460c9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,69 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.UI;
[NetworkSettings(channel = 0, sendInterval = 0)]
public class serverRunningMenu : NetworkBehaviour
{
public Text clientInfo;
public Text uptime;
public Text throughput;
private float startTime = 0;
private bool hasStarted = false;
public carCount carCounter;
public GameObject next;
public NetworkManager network;
private float timePassed = 0;
//WebGL-ben build hibát okozna.
#if UNITY_STANDALONE
public void Update ()
{
if (!hasStarted)
{
startTime = Time.time;
hasStarted = true;
}
if (timePassed < 1) timePassed += Time.deltaTime;
else
{
Debug.Log(NetworkServer.connections);
Debug.Log(NetworkServer.connections.Count);
timePassed = 0;
clientInfo.text = "Clients:\n";
foreach (NetworkConnection connection in NetworkServer.connections) {
if(connection != null)
{
/*int unused;
int bytesSent = 0;
connection.GetStatsOut(out unused, out unused, out bytesSent, out unused);*/
clientInfo.text += (connection.address.ToString() == "" ? "websocket connection" : connection.address.ToString()) + "\n";
}
}
throughput.text = "Cars passed: " + carCounter.throughput.ToString();
float timeInS = Time.time - startTime;
uptime.text = "Uptime: " + Mathf.Round(timeInS / 60 / 60).ToString() + "h " + Mathf.Round(timeInS % ( 60 * 60 ) / 60).ToString() + "m " + Mathf.Round(timeInS % 60).ToString() + "s";
}
}
public void shutdownServer ()
{
hasStarted = false;
network.StopServer();
gameObject.SetActive(false);
next.SetActive(true);
}
#endif
}

View file

@ -0,0 +1,33 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.UI;
[NetworkSettings(channel = 0, sendInterval = 0)]
public class serverStartMenu : NetworkBehaviour
{
public TrafficController traffic;
public tweetPoller tweet;
public InputField trafficAddress;
public InputField eventAddress;
public GameObject next;
public NetworkManager network;
public void createServer ()
{
if(trafficAddress.text != "")
traffic.url = trafficAddress.text;
if (eventAddress.text != "")
tweet.url = eventAddress.text;
network.StartServer();
gameObject.SetActive(false);
next.SetActive(true);
}
}

View file

@ -0,0 +1,33 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.UI;
[NetworkSettings(channel = 0, sendInterval = 0)]
public class serverStartMenu : NetworkBehaviour
{
public TrafficController traffic;
public tweetPoller tweet;
public InputField trafficAddress;
public InputField eventAddress;
public GameObject next;
public NetworkManager network;
public void createServer ()
{
if(trafficAddress.text != "")
traffic.url = trafficAddress.text;
if (eventAddress.text != "")
tweet.url = eventAddress.text;
network.StartServer();
gameObject.SetActive(false);
next.SetActive(true);
}
}

Some files were not shown because too many files have changed in this diff Show more