315 lines
8.3 KiB
C#
315 lines
8.3 KiB
C#
using System;
|
|
using System.Collections;
|
|
using UnityEngine;
|
|
using UnityEngine.Video;
|
|
|
|
public class TimingWheelShooter : MonoBehaviour, ITask
|
|
{
|
|
[Header("Task Info")]
|
|
[SerializeField] private string taskID = "RING_SHOOTER_01";
|
|
[SerializeField] private string taskName = "Break the Ring";
|
|
[SerializeField] private TaskType taskType = TaskType.Task;
|
|
[SerializeField] private Vector2 taskLocation = Vector2.zero;
|
|
|
|
public string TaskID => taskID;
|
|
public TaskType TaskType => taskType;
|
|
public string TaskName => taskName;
|
|
public (double, double) TaskLocation => (taskLocation.x, taskLocation.y);
|
|
public bool IsCompleted { get; private set; }
|
|
|
|
[Header("Wheel")]
|
|
public Transform rotatingWheel;
|
|
public float defaultAngle = 90f;
|
|
public float sweepAngle = 90f;
|
|
public float wheelSpeed = 120f;
|
|
|
|
[Header("Projectile")]
|
|
public ProjectileBehaviour potatoPrefab;
|
|
public Transform projectileSpawn;
|
|
public Transform aimPoint;
|
|
public GlassRingController ring;
|
|
public float baseLaunchSpeed = 12f;
|
|
|
|
[Header("Audio")]
|
|
public AudioSource audioSource;
|
|
public AudioClip startClip;
|
|
[Range(0f, 1f)] public float startVolume = 1f;
|
|
public AudioClip winClip;
|
|
[Range(0f, 1f)] public float winVolume = 1f;
|
|
|
|
[Header("Finish Video")]
|
|
public VideoPlayer videoPlayer;
|
|
public VideoClip finishVideo;
|
|
public AudioClip finishVideoAudio;
|
|
[Range(0f, 1f)] public float finishVideoAudioVolume = 1f;
|
|
public Camera targetCamera;
|
|
|
|
[Header("State")]
|
|
public bool autoRestartAfterShot = true;
|
|
public float restartDelay = 0.75f;
|
|
|
|
private bool isRunning = true;
|
|
private bool finishSequenceStarted = false;
|
|
private float currentAngle;
|
|
private float direction = 1f;
|
|
private Action<ITask> onCompletedCallback;
|
|
|
|
private void Awake()
|
|
{
|
|
if (audioSource == null)
|
|
audioSource = GetComponent<AudioSource>();
|
|
|
|
if (audioSource == null)
|
|
audioSource = gameObject.AddComponent<AudioSource>();
|
|
|
|
if (videoPlayer == null)
|
|
videoPlayer = GetComponent<VideoPlayer>();
|
|
|
|
if (videoPlayer == null)
|
|
videoPlayer = gameObject.AddComponent<VideoPlayer>();
|
|
|
|
if (targetCamera == null)
|
|
targetCamera = Camera.main;
|
|
|
|
// Video setup
|
|
videoPlayer.playOnAwake = false;
|
|
videoPlayer.waitForFirstFrame = true;
|
|
videoPlayer.skipOnDrop = true;
|
|
videoPlayer.isLooping = false;
|
|
videoPlayer.audioOutputMode = VideoAudioOutputMode.None;
|
|
videoPlayer.renderMode = VideoRenderMode.CameraNearPlane;
|
|
|
|
if (targetCamera != null)
|
|
videoPlayer.targetCamera = targetCamera;
|
|
}
|
|
|
|
private void Start()
|
|
{
|
|
currentAngle = defaultAngle;
|
|
SetWheelAngle(currentAngle);
|
|
|
|
PlayStartSound();
|
|
}
|
|
|
|
private void Update()
|
|
{
|
|
if (IsCompleted || finishSequenceStarted) return;
|
|
|
|
if (isRunning)
|
|
UpdateWheelMotion();
|
|
|
|
if (Input.GetMouseButtonDown(0))
|
|
StopAndShoot();
|
|
|
|
if (Input.touchCount > 0 && Input.GetTouch(0).phase == TouchPhase.Began)
|
|
StopAndShoot();
|
|
}
|
|
|
|
private void UpdateWheelMotion()
|
|
{
|
|
float halfRange = sweepAngle * 0.5f;
|
|
float minAngle = defaultAngle - halfRange;
|
|
float maxAngle = defaultAngle + halfRange;
|
|
|
|
currentAngle += direction * wheelSpeed * Time.deltaTime;
|
|
|
|
if (currentAngle >= maxAngle)
|
|
{
|
|
currentAngle = maxAngle;
|
|
direction = -1f;
|
|
}
|
|
else if (currentAngle <= minAngle)
|
|
{
|
|
currentAngle = minAngle;
|
|
direction = 1f;
|
|
}
|
|
|
|
SetWheelAngle(currentAngle);
|
|
}
|
|
|
|
private void SetWheelAngle(float angle)
|
|
{
|
|
if (rotatingWheel == null) return;
|
|
|
|
Vector3 e = rotatingWheel.localEulerAngles;
|
|
e.y = angle; // your current setup uses Y
|
|
rotatingWheel.localEulerAngles = e;
|
|
}
|
|
|
|
private float EvaluateMultiplier(float angle)
|
|
{
|
|
float halfRange = sweepAngle * 0.5f;
|
|
float offset = Mathf.Abs(angle - defaultAngle);
|
|
|
|
float zone5 = halfRange / 2.5f;
|
|
float zone2 = halfRange / 1.5f;
|
|
|
|
if (offset <= zone5) return 5f;
|
|
if (offset <= zone2) return 2f;
|
|
return 1f;
|
|
}
|
|
|
|
public void StopAndShoot()
|
|
{
|
|
if (!isRunning || IsCompleted || finishSequenceStarted) return;
|
|
|
|
isRunning = false;
|
|
|
|
float multiplier = EvaluateMultiplier(currentAngle);
|
|
Shoot(multiplier);
|
|
|
|
Debug.Log($"Wheel stopped at {currentAngle:F1}°, multiplier = {multiplier}x");
|
|
|
|
if (autoRestartAfterShot)
|
|
Invoke(nameof(RestartWheel), restartDelay);
|
|
}
|
|
|
|
private void Shoot(float multiplier)
|
|
{
|
|
if (potatoPrefab == null || projectileSpawn == null || aimPoint == null)
|
|
return;
|
|
|
|
Vector3 dir = (aimPoint.position - projectileSpawn.position).normalized;
|
|
float speed = baseLaunchSpeed * multiplier;
|
|
|
|
ProjectileBehaviour p = Instantiate(
|
|
potatoPrefab,
|
|
projectileSpawn.position,
|
|
Quaternion.LookRotation(dir, Vector3.up)
|
|
);
|
|
|
|
p.baseDamage *= multiplier;
|
|
p.breakImpulse *= multiplier;
|
|
p.Launch(ring, dir * speed, 1f);
|
|
}
|
|
|
|
private void RestartWheel()
|
|
{
|
|
if (!IsCompleted && !finishSequenceStarted)
|
|
isRunning = true;
|
|
}
|
|
|
|
public void NotifyButtonHit(ProjectileBehaviour projectile)
|
|
{
|
|
if (IsCompleted || finishSequenceStarted) return;
|
|
|
|
Debug.Log("Diamond hit. Starting finish sequence.");
|
|
StartCoroutine(PlayFinishSequenceThenComplete());
|
|
}
|
|
|
|
private IEnumerator PlayFinishSequenceThenComplete()
|
|
{
|
|
finishSequenceStarted = true;
|
|
isRunning = false;
|
|
CancelInvoke();
|
|
|
|
if (ring != null)
|
|
ring.enabled = false;
|
|
|
|
if (finishVideo == null)
|
|
{
|
|
if (audioSource != null && winClip != null)
|
|
audioSource.PlayOneShot(winClip, winVolume);
|
|
|
|
Complete();
|
|
yield break;
|
|
}
|
|
|
|
videoPlayer.Stop();
|
|
videoPlayer.clip = finishVideo;
|
|
videoPlayer.renderMode = VideoRenderMode.CameraNearPlane;
|
|
|
|
if (targetCamera == null)
|
|
targetCamera = Camera.main;
|
|
|
|
if (targetCamera != null)
|
|
videoPlayer.targetCamera = targetCamera;
|
|
|
|
bool finished = false;
|
|
void OnVideoFinished(VideoPlayer vp) => finished = true;
|
|
|
|
videoPlayer.loopPointReached += OnVideoFinished;
|
|
|
|
videoPlayer.Prepare();
|
|
while (!videoPlayer.isPrepared)
|
|
yield return null;
|
|
|
|
videoPlayer.Play();
|
|
|
|
if (audioSource != null && finishVideoAudio != null)
|
|
{
|
|
audioSource.clip = finishVideoAudio;
|
|
audioSource.volume = finishVideoAudioVolume;
|
|
audioSource.Play();
|
|
}
|
|
else if (audioSource != null && winClip != null)
|
|
{
|
|
audioSource.PlayOneShot(winClip, winVolume);
|
|
}
|
|
|
|
while (!finished)
|
|
yield return null;
|
|
|
|
videoPlayer.loopPointReached -= OnVideoFinished;
|
|
videoPlayer.Stop();
|
|
|
|
Complete();
|
|
}
|
|
|
|
public void Initialize(Action<ITask> onCompleted)
|
|
{
|
|
onCompletedCallback = onCompleted;
|
|
IsCompleted = false;
|
|
finishSequenceStarted = false;
|
|
isRunning = true;
|
|
direction = 1f;
|
|
currentAngle = defaultAngle;
|
|
SetWheelAngle(currentAngle);
|
|
|
|
CancelInvoke();
|
|
|
|
if (ring != null)
|
|
ring.enabled = true;
|
|
|
|
if (audioSource != null)
|
|
audioSource.Stop();
|
|
|
|
if (videoPlayer != null)
|
|
videoPlayer.Stop();
|
|
|
|
PlayStartSound();
|
|
}
|
|
|
|
public void ExitTask(Action<ITask> onExit)
|
|
{
|
|
isRunning = false;
|
|
finishSequenceStarted = false;
|
|
CancelInvoke();
|
|
|
|
if (audioSource != null)
|
|
audioSource.Stop();
|
|
|
|
if (videoPlayer != null)
|
|
videoPlayer.Stop();
|
|
|
|
onExit?.Invoke(this);
|
|
}
|
|
|
|
public void Complete()
|
|
{
|
|
if (IsCompleted) return;
|
|
|
|
IsCompleted = true;
|
|
isRunning = false;
|
|
finishSequenceStarted = false;
|
|
CancelInvoke();
|
|
|
|
onCompletedCallback?.Invoke(this);
|
|
}
|
|
|
|
private void PlayStartSound()
|
|
{
|
|
if (audioSource != null && startClip != null)
|
|
audioSource.PlayOneShot(startClip, startVolume);
|
|
}
|
|
} |