329 lines
10 KiB
C#
329 lines
10 KiB
C#
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
|
|
public class player : MonoBehaviour
|
|
{
|
|
[Header ("Movement Settings")]
|
|
public float speed;
|
|
public float jumpscale;
|
|
|
|
[Header("Ground & Wall Detection")]
|
|
public bool isground, iswall;
|
|
public float rayLength = 0.3f;
|
|
public LayerMask groundLayer;
|
|
|
|
[Header("Game References")]
|
|
public GameObject soal;
|
|
public GameObject gameover;
|
|
public GameObject finish;
|
|
public healthmanager healthmanager;
|
|
|
|
[Header("Audio")]
|
|
public AudioSource jump_audio;
|
|
public AudioSource soal_audio;
|
|
public AudioSource walk_audio;
|
|
public AudioSource fall_audio;
|
|
public AudioSource finish_audio;
|
|
public AudioSource gameover_audio;
|
|
|
|
//private references
|
|
private Rigidbody2D body;
|
|
private SpriteRenderer sprite;
|
|
private Animator animator;
|
|
private Vector3 safePosition;
|
|
private SoalPapanManager soalManager;
|
|
private skor_akhir UpdateLevel;
|
|
private BoxCollider2D wall_detector;
|
|
private checkpoint[] allCheckpoints;
|
|
private bool isRespawning = false;
|
|
private bool canJump = true;
|
|
private bool justRespawned = false;
|
|
|
|
//water detection
|
|
private bool inWater = false;
|
|
private float WaterDangerThreshold = -6.5f;
|
|
private float respawnYOffset = 0.5f;
|
|
|
|
void Start()
|
|
{
|
|
soalManager = soal.GetComponent<SoalPapanManager>();
|
|
body = GetComponent<Rigidbody2D>();
|
|
sprite = GetComponent<SpriteRenderer>();
|
|
animator = GetComponent<Animator>();
|
|
|
|
//set initial safe position;
|
|
safePosition = transform.position;
|
|
}
|
|
|
|
[Obsolete]
|
|
void Awake() {
|
|
allCheckpoints = GameObject.FindObjectsOfType<checkpoint>();
|
|
}
|
|
|
|
void Update()
|
|
{
|
|
if (isRespawning) return;
|
|
|
|
UpdateGroundWallCheck();
|
|
|
|
if (isground && !canJump && !justRespawned && Mathf.Abs(body.linearVelocity.y) < 0.1f)
|
|
{
|
|
canJump = true;
|
|
Debug.Log("Landed: canJump reset");
|
|
}
|
|
|
|
//handle input
|
|
if (Input.GetKey(KeyCode.RightArrow))
|
|
{
|
|
runright();
|
|
}
|
|
if (Input.GetKey(KeyCode.LeftArrow))
|
|
{
|
|
runleft();
|
|
}
|
|
if (Input.GetKeyUp(KeyCode.RightArrow) || Input.GetKeyUp(KeyCode.LeftArrow))
|
|
{
|
|
stop();
|
|
}
|
|
if (Input.GetKeyDown(KeyCode.UpArrow))
|
|
{
|
|
jump();
|
|
}
|
|
|
|
UpdateAnimation();
|
|
UpdateSafePosition();
|
|
CheckWaterFall();
|
|
|
|
Debug.DrawRay(transform.position, Vector2.down * rayLength, Color.red);
|
|
}
|
|
|
|
void UpdateGroundWallCheck()
|
|
{
|
|
RaycastHit2D hitGround = Physics2D.Raycast(transform.position, Vector2.down, rayLength, groundLayer);
|
|
isground = hitGround.collider != null;
|
|
iswall = Physics2D.Raycast(transform.position, Vector2.right, 0.3f, groundLayer).collider != null ||
|
|
Physics2D.Raycast(transform.position, Vector2.left, 0.3f, groundLayer).collider != null;
|
|
}
|
|
|
|
void UpdateSafePosition() {
|
|
//update safe position when player is on solid ground (not water)
|
|
if (Mathf.Abs(body.linearVelocity.y) < 0.1f && isground) {
|
|
RaycastHit2D hit = Physics2D.Raycast(transform.position, Vector2.down, 1.5f, groundLayer);
|
|
if (hit.collider != null && !hit.collider.CompareTag("Water")) {
|
|
safePosition = new Vector3(transform.position.x, hit.point.y + respawnYOffset, transform.position.z);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CheckWaterFall() {
|
|
//check if player has fallen bellow the water danger threshold
|
|
if(transform.position.y < WaterDangerThreshold && !inWater) {
|
|
inWater = true;
|
|
HandleWaterFall();
|
|
}
|
|
|
|
//reset water detection when player is back above threshold
|
|
if(transform.position.y >= WaterDangerThreshold) {
|
|
inWater = false;
|
|
}
|
|
}
|
|
|
|
void HandleWaterFall()
|
|
{
|
|
//stop player movement
|
|
body.linearVelocity = Vector2.zero;
|
|
body.gravityScale = 0;
|
|
|
|
//disable camera flow
|
|
Camera.main.GetComponent<camerafollow>().enabled = false;
|
|
|
|
//reduce health
|
|
healthmanager.TakeDamage(1);
|
|
Debug.Log("Nyawa tersisa: " + healthmanager.health);
|
|
|
|
// play fall sound
|
|
if (Time.timeScale == 1)
|
|
{
|
|
fall_audio.Play();
|
|
}
|
|
|
|
// ✅ Ambil checkpoint terdekat
|
|
checkpoint nearestCP = FindNearestCheckpoint();
|
|
if (nearestCP != null)
|
|
{
|
|
safePosition = new Vector3(nearestCP.transform.position.x, nearestCP.transform.position.y + respawnYOffset, transform.position.z);
|
|
Debug.Log("Respawn ke checkpoint terdekat: " + nearestCP.name);
|
|
}
|
|
|
|
canJump = false;
|
|
justRespawned = true;
|
|
|
|
//check if player is still alive
|
|
if (healthmanager.isAlive())
|
|
{
|
|
StartCoroutine(RespawnRoutine());
|
|
}
|
|
else
|
|
{
|
|
StartCoroutine(GameOverRoutine());
|
|
}
|
|
}
|
|
bool IsSoalSedangAktif()
|
|
{
|
|
|
|
return soalManager != null && soalManager.IsSoalAktif();
|
|
}
|
|
public void runleft() {
|
|
sprite.flipX = true;
|
|
if (!IsSoalSedangAktif() && !gameover.activeSelf && !finish.activeSelf) {
|
|
body.linearVelocity = new Vector2(-speed, body.linearVelocity.y);
|
|
}
|
|
}
|
|
|
|
public void runright() {
|
|
sprite.flipX = false;
|
|
if (!IsSoalSedangAktif() && !gameover.activeSelf && !finish.activeSelf) {
|
|
body.linearVelocity = new Vector2(speed, body.linearVelocity.y);
|
|
}
|
|
}
|
|
|
|
public void stop() {
|
|
if (!IsSoalSedangAktif() && !gameover.activeSelf && !finish.activeSelf) {
|
|
body.linearVelocity = new Vector2(0, body.linearVelocity.y);
|
|
}
|
|
}
|
|
|
|
public void jump() {
|
|
if (justRespawned || !isground || !canJump)
|
|
return;
|
|
|
|
if (!IsSoalSedangAktif() && !gameover.activeSelf && !finish.activeSelf) {
|
|
body.linearVelocity = new Vector2(body.linearVelocity.x, jumpscale);
|
|
canJump = false;
|
|
if (Time.timeScale == 1) jump_audio.Play();
|
|
}
|
|
}
|
|
|
|
void UpdateAnimation() {
|
|
if(body.linearVelocity.y > 0.01f){
|
|
animator.SetInteger("state", 2);
|
|
walk_audio.Stop();
|
|
} else if(body.linearVelocity.y < -0.01f){
|
|
animator.SetInteger("state", 3);
|
|
walk_audio.Stop();
|
|
} else {
|
|
if(Math.Abs(body.linearVelocity.x) > speed/2f){
|
|
animator.SetInteger("state", 1);
|
|
if(!walk_audio.isPlaying && isground){
|
|
if(Time.timeScale == 1){
|
|
walk_audio.Play();
|
|
}
|
|
}
|
|
} else {
|
|
animator.SetInteger("state", 0);
|
|
walk_audio.Stop();
|
|
}
|
|
}
|
|
}
|
|
|
|
void OnTriggerEnter2D(Collider2D obj)
|
|
{
|
|
//if player touches water directly
|
|
if(obj.CompareTag("Water") && transform.position.y < WaterDangerThreshold && !isRespawning) {
|
|
HandleWaterFall();
|
|
}
|
|
|
|
// Jika menyentuh checkpoint
|
|
if (obj.CompareTag("Checkpoint")) {
|
|
safePosition = new Vector3(obj.transform.position.x, obj.transform.position.y + respawnYOffset, transform.position.z);
|
|
Debug.Log("Checkpoint updated at: " + safePosition);
|
|
}
|
|
|
|
//handle question activation
|
|
if (obj.CompareTag("pos")){
|
|
if (soalManager != null && !soalManager.IsSoalAktif())
|
|
{
|
|
soalManager.TampilkanSoal();
|
|
obj.gameObject.SetActive(false); // disable trigger papan
|
|
soal_audio.Play();
|
|
}
|
|
/*soal.transform.GetChild(obj.transform.GetSiblingIndex()).gameObject.SetActive(true);
|
|
obj.gameObject.SetActive(false);
|
|
soal_audio.Play();*/
|
|
}
|
|
|
|
//handle finish line
|
|
if(obj.CompareTag("Finish")) {
|
|
if (soal.GetComponent<SoalPapanManager>().soalterjawab == 5)
|
|
{
|
|
finish.SetActive(true);
|
|
finish_audio.Play();
|
|
}
|
|
}
|
|
}
|
|
|
|
IEnumerator RespawnRoutine()
|
|
{
|
|
isRespawning = true;
|
|
//yield return new WaitForSeconds(0.3f);
|
|
body.gravityScale = 2;
|
|
body.linearVelocity = Vector2.zero;
|
|
transform.position = safePosition;
|
|
Camera.main.GetComponent<camerafollow>().enabled = true;
|
|
Camera.main.GetComponent<camerafollow>().player = this.transform;
|
|
|
|
inWater = false;
|
|
yield return new WaitUntil(() =>
|
|
{
|
|
UpdateGroundWallCheck();
|
|
return isground && Mathf.Abs(body.linearVelocity.y) < 0.05f;
|
|
});
|
|
|
|
canJump = true;
|
|
yield return new WaitForSeconds(0.2f);
|
|
justRespawned = false;
|
|
isRespawning = false;
|
|
|
|
Debug.Log($"Respawn complete. isground: {isground}, canJump: {canJump}");
|
|
}
|
|
|
|
IEnumerator GameOverRoutine(){
|
|
yield return new WaitForSeconds(0.3f);
|
|
gameover.SetActive(true);
|
|
gameover_audio.Play();
|
|
Debug.Log("Game Over!");
|
|
}
|
|
|
|
checkpoint FindNearestCheckpoint() {
|
|
checkpoint nearest = null;
|
|
float minDistance = Mathf.Infinity;
|
|
Vector3 pos = transform.position;
|
|
|
|
foreach (checkpoint cp in allCheckpoints) {
|
|
float dist = Vector3.Distance(pos, cp.transform.position);
|
|
if (dist < minDistance) {
|
|
minDistance = dist;
|
|
nearest = cp;
|
|
}
|
|
}
|
|
return nearest;
|
|
}
|
|
|
|
// Visualize the raycasts for debugging
|
|
void OnDrawGizmos() {
|
|
// Ground check
|
|
Gizmos.color = Color.red;
|
|
Gizmos.DrawRay(transform.position, Vector2.down * rayLength);
|
|
|
|
// Jump check
|
|
Gizmos.color = Color.blue;
|
|
Gizmos.DrawRay(transform.position, Vector2.down * 0.6f);
|
|
|
|
// Wall checks
|
|
Gizmos.color = Color.yellow;
|
|
Gizmos.DrawRay(transform.position, Vector2.right * 0.3f);
|
|
Gizmos.DrawRay(transform.position, Vector2.left * 0.3f);
|
|
}
|
|
} |