Import 2D Level
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Platformer.Core;
|
||||
using Platformer.Model;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Platformer.Mechanics
|
||||
{
|
||||
/// <summary>
|
||||
/// AnimationController integrates physics and animation. It is generally used for simple enemy animation.
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(SpriteRenderer), typeof(Animator))]
|
||||
public class AnimationController : KinematicObject
|
||||
{
|
||||
/// <summary>
|
||||
/// Max horizontal speed.
|
||||
/// </summary>
|
||||
public float maxSpeed = 7;
|
||||
/// <summary>
|
||||
/// Max jump velocity
|
||||
/// </summary>
|
||||
public float jumpTakeOffSpeed = 7;
|
||||
|
||||
/// <summary>
|
||||
/// Used to indicated desired direction of travel.
|
||||
/// </summary>
|
||||
public Vector2 move;
|
||||
|
||||
/// <summary>
|
||||
/// Set to true to initiate a jump.
|
||||
/// </summary>
|
||||
public bool jump;
|
||||
|
||||
/// <summary>
|
||||
/// Set to true to set the current jump velocity to zero.
|
||||
/// </summary>
|
||||
public bool stopJump;
|
||||
|
||||
SpriteRenderer spriteRenderer;
|
||||
Animator animator;
|
||||
PlatformerModel model = Simulation.GetModel<PlatformerModel>();
|
||||
|
||||
protected virtual void Awake()
|
||||
{
|
||||
spriteRenderer = GetComponent<SpriteRenderer>();
|
||||
animator = GetComponent<Animator>();
|
||||
}
|
||||
|
||||
protected override void ComputeVelocity()
|
||||
{
|
||||
if (jump && IsGrounded)
|
||||
{
|
||||
velocity.y = jumpTakeOffSpeed * model.jumpModifier;
|
||||
jump = false;
|
||||
}
|
||||
else if (stopJump)
|
||||
{
|
||||
stopJump = false;
|
||||
if (velocity.y > 0)
|
||||
{
|
||||
velocity.y = velocity.y * model.jumpDeceleration;
|
||||
}
|
||||
}
|
||||
|
||||
if (move.x > 0.01f)
|
||||
spriteRenderer.flipX = false;
|
||||
else if (move.x < -0.01f)
|
||||
spriteRenderer.flipX = true;
|
||||
|
||||
animator.SetBool("grounded", IsGrounded);
|
||||
animator.SetFloat("velocityX", Mathf.Abs(velocity.x) / maxSpeed);
|
||||
|
||||
targetVelocity = move * maxSpeed;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Platformer.Gameplay;
|
||||
using UnityEngine;
|
||||
using static Platformer.Core.Simulation;
|
||||
|
||||
namespace Platformer.Mechanics
|
||||
{
|
||||
/// <summary>
|
||||
/// DeathZone components mark a collider which will schedule a
|
||||
/// PlayerEnteredDeathZone event when the player enters the trigger.
|
||||
/// </summary>
|
||||
public class DeathZone : MonoBehaviour
|
||||
{
|
||||
void OnTriggerEnter2D(Collider2D collider)
|
||||
{
|
||||
var p = collider.gameObject.GetComponent<PlayerController>();
|
||||
if (p != null)
|
||||
{
|
||||
var ev = Schedule<PlayerEnteredDeathZone>();
|
||||
ev.deathzone = this;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Platformer.Gameplay;
|
||||
using UnityEngine;
|
||||
using static Platformer.Core.Simulation;
|
||||
|
||||
namespace Platformer.Mechanics
|
||||
{
|
||||
/// <summary>
|
||||
/// A simple controller for enemies. Provides movement control over a patrol path.
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(AnimationController), typeof(Collider2D))]
|
||||
public class EnemyController : MonoBehaviour
|
||||
{
|
||||
public PatrolPath path;
|
||||
public AudioClip ouch;
|
||||
|
||||
internal PatrolPath.Mover mover;
|
||||
internal AnimationController control;
|
||||
internal Collider2D _collider;
|
||||
internal AudioSource _audio;
|
||||
SpriteRenderer spriteRenderer;
|
||||
|
||||
public Bounds Bounds => _collider.bounds;
|
||||
|
||||
void Awake()
|
||||
{
|
||||
control = GetComponent<AnimationController>();
|
||||
_collider = GetComponent<Collider2D>();
|
||||
_audio = GetComponent<AudioSource>();
|
||||
spriteRenderer = GetComponent<SpriteRenderer>();
|
||||
}
|
||||
|
||||
void OnCollisionEnter2D(Collision2D collision)
|
||||
{
|
||||
var player = collision.gameObject.GetComponent<PlayerController>();
|
||||
if (player != null)
|
||||
{
|
||||
var ev = Schedule<PlayerEnemyCollision>();
|
||||
ev.player = player;
|
||||
ev.enemy = this;
|
||||
}
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
if (path != null)
|
||||
{
|
||||
if (mover == null) mover = path.CreateMover(control.maxSpeed * 0.5f);
|
||||
control.move.x = Mathf.Clamp(mover.Position.x - transform.position.x, -1, 1);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
using Platformer.Core;
|
||||
using Platformer.Model;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Platformer.Mechanics
|
||||
{
|
||||
/// <summary>
|
||||
/// This class exposes the the game model in the inspector, and ticks the
|
||||
/// simulation.
|
||||
/// </summary>
|
||||
public class GameController : MonoBehaviour
|
||||
{
|
||||
public static GameController Instance { get; private set; }
|
||||
|
||||
//This model field is public and can be therefore be modified in the
|
||||
//inspector.
|
||||
//The reference actually comes from the InstanceRegister, and is shared
|
||||
//through the simulation and events. Unity will deserialize over this
|
||||
//shared reference when the scene loads, allowing the model to be
|
||||
//conveniently configured inside the inspector.
|
||||
public PlatformerModel model = Simulation.GetModel<PlatformerModel>();
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
Instance = this;
|
||||
}
|
||||
|
||||
void OnDisable()
|
||||
{
|
||||
if (Instance == this) Instance = null;
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
if (Instance == this) Simulation.Tick();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
using System;
|
||||
using Platformer.Gameplay;
|
||||
using UnityEngine;
|
||||
using static Platformer.Core.Simulation;
|
||||
|
||||
namespace Platformer.Mechanics
|
||||
{
|
||||
/// <summary>
|
||||
/// Represebts the current vital statistics of some game entity.
|
||||
/// </summary>
|
||||
public class Health : MonoBehaviour
|
||||
{
|
||||
/// <summary>
|
||||
/// The maximum hit points for the entity.
|
||||
/// </summary>
|
||||
public int maxHP = 1;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates if the entity should be considered 'alive'.
|
||||
/// </summary>
|
||||
public bool IsAlive => currentHP > 0;
|
||||
|
||||
int currentHP;
|
||||
|
||||
/// <summary>
|
||||
/// Increment the HP of the entity.
|
||||
/// </summary>
|
||||
public void Increment()
|
||||
{
|
||||
currentHP = Mathf.Clamp(currentHP + 1, 0, maxHP);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decrement the HP of the entity. Will trigger a HealthIsZero event when
|
||||
/// current HP reaches 0.
|
||||
/// </summary>
|
||||
public void Decrement()
|
||||
{
|
||||
currentHP = Mathf.Clamp(currentHP - 1, 0, maxHP);
|
||||
if (currentHP == 0)
|
||||
{
|
||||
var ev = Schedule<HealthIsZero>();
|
||||
ev.health = this;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decrement the HP of the entitiy until HP reaches 0.
|
||||
/// </summary>
|
||||
public void Die()
|
||||
{
|
||||
while (currentHP > 0) Decrement();
|
||||
}
|
||||
|
||||
void Awake()
|
||||
{
|
||||
currentHP = maxHP;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Platformer.Mechanics
|
||||
{
|
||||
/// <summary>
|
||||
/// Implements game physics for some in game entity.
|
||||
/// </summary>
|
||||
public class KinematicObject : MonoBehaviour
|
||||
{
|
||||
/// <summary>
|
||||
/// The minimum normal (dot product) considered suitable for the entity sit on.
|
||||
/// </summary>
|
||||
public float minGroundNormalY = .65f;
|
||||
|
||||
/// <summary>
|
||||
/// A custom gravity coefficient applied to this entity.
|
||||
/// </summary>
|
||||
public float gravityModifier = 1f;
|
||||
|
||||
/// <summary>
|
||||
/// The current velocity of the entity.
|
||||
/// </summary>
|
||||
public Vector2 velocity;
|
||||
|
||||
/// <summary>
|
||||
/// Is the entity currently sitting on a surface?
|
||||
/// </summary>
|
||||
/// <value></value>
|
||||
public bool IsGrounded { get; private set; }
|
||||
|
||||
protected Vector2 targetVelocity;
|
||||
protected Vector2 groundNormal;
|
||||
protected Rigidbody2D body;
|
||||
protected ContactFilter2D contactFilter;
|
||||
protected RaycastHit2D[] hitBuffer = new RaycastHit2D[16];
|
||||
|
||||
protected const float minMoveDistance = 0.001f;
|
||||
protected const float shellRadius = 0.01f;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Bounce the object's vertical velocity.
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
public void Bounce(float value)
|
||||
{
|
||||
velocity.y = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Bounce the objects velocity in a direction.
|
||||
/// </summary>
|
||||
/// <param name="dir"></param>
|
||||
public void Bounce(Vector2 dir)
|
||||
{
|
||||
velocity.y = dir.y;
|
||||
velocity.x = dir.x;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Teleport to some position.
|
||||
/// </summary>
|
||||
/// <param name="position"></param>
|
||||
public void Teleport(Vector3 position)
|
||||
{
|
||||
body.position = position;
|
||||
velocity *= 0;
|
||||
body.linearVelocity *= 0;
|
||||
}
|
||||
|
||||
protected virtual void OnEnable()
|
||||
{
|
||||
body = GetComponent<Rigidbody2D>();
|
||||
body.bodyType = RigidbodyType2D.Kinematic;
|
||||
}
|
||||
|
||||
protected virtual void OnDisable()
|
||||
{
|
||||
body.bodyType = RigidbodyType2D.Dynamic;
|
||||
}
|
||||
|
||||
protected virtual void Start()
|
||||
{
|
||||
contactFilter.useTriggers = false;
|
||||
contactFilter.SetLayerMask(Physics2D.GetLayerCollisionMask(gameObject.layer));
|
||||
contactFilter.useLayerMask = true;
|
||||
}
|
||||
|
||||
protected virtual void Update()
|
||||
{
|
||||
targetVelocity = Vector2.zero;
|
||||
ComputeVelocity();
|
||||
}
|
||||
|
||||
protected virtual void ComputeVelocity()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
protected virtual void FixedUpdate()
|
||||
{
|
||||
//if already falling, fall faster than the jump speed, otherwise use normal gravity.
|
||||
if (velocity.y < 0)
|
||||
velocity += gravityModifier * Physics2D.gravity * Time.deltaTime;
|
||||
else
|
||||
velocity += Physics2D.gravity * Time.deltaTime;
|
||||
|
||||
velocity.x = targetVelocity.x;
|
||||
|
||||
IsGrounded = false;
|
||||
|
||||
var deltaPosition = velocity * Time.deltaTime;
|
||||
|
||||
var moveAlongGround = new Vector2(groundNormal.y, -groundNormal.x);
|
||||
|
||||
var move = moveAlongGround * deltaPosition.x;
|
||||
|
||||
PerformMovement(move, false);
|
||||
|
||||
move = Vector2.up * deltaPosition.y;
|
||||
|
||||
PerformMovement(move, true);
|
||||
|
||||
}
|
||||
|
||||
void PerformMovement(Vector2 move, bool yMovement)
|
||||
{
|
||||
var distance = move.magnitude;
|
||||
|
||||
if (distance > minMoveDistance)
|
||||
{
|
||||
//check if we hit anything in current direction of travel
|
||||
var count = body.Cast(move, contactFilter, hitBuffer, distance + shellRadius);
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var currentNormal = hitBuffer[i].normal;
|
||||
|
||||
//is this surface flat enough to land on?
|
||||
if (currentNormal.y > minGroundNormalY)
|
||||
{
|
||||
IsGrounded = true;
|
||||
// if moving up, change the groundNormal to new surface normal.
|
||||
if (yMovement)
|
||||
{
|
||||
groundNormal = currentNormal;
|
||||
currentNormal.x = 0;
|
||||
}
|
||||
}
|
||||
if (IsGrounded)
|
||||
{
|
||||
//how much of our velocity aligns with surface normal?
|
||||
var projection = Vector2.Dot(velocity, currentNormal);
|
||||
if (projection < 0)
|
||||
{
|
||||
//slower velocity if moving against the normal (up a hill).
|
||||
velocity = velocity - projection * currentNormal;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//We are airborne, but hit something, so cancel vertical up and horizontal velocity.
|
||||
velocity.x *= 0;
|
||||
velocity.y = Mathf.Min(velocity.y, 0);
|
||||
}
|
||||
//remove shellDistance from actual move distance.
|
||||
var modifiedDistance = hitBuffer[i].distance - shellRadius;
|
||||
distance = modifiedDistance < distance ? modifiedDistance : distance;
|
||||
}
|
||||
}
|
||||
body.position = body.position + move.normalized * distance;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Platformer.Mechanics
|
||||
{
|
||||
public partial class PatrolPath
|
||||
{
|
||||
/// <summary>
|
||||
/// The Mover class oscillates between start and end points of a path at a defined speed.
|
||||
/// </summary>
|
||||
public class Mover
|
||||
{
|
||||
PatrolPath path;
|
||||
float p = 0;
|
||||
float duration;
|
||||
float startTime;
|
||||
|
||||
public Mover(PatrolPath path, float speed)
|
||||
{
|
||||
this.path = path;
|
||||
this.duration = (path.endPosition - path.startPosition).magnitude / speed;
|
||||
this.startTime = Time.time;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the position of the mover for the current frame.
|
||||
/// </summary>
|
||||
/// <value></value>
|
||||
public Vector2 Position
|
||||
{
|
||||
get
|
||||
{
|
||||
p = Mathf.InverseLerp(0, duration, Mathf.PingPong(Time.time - startTime, duration));
|
||||
return path.transform.TransformPoint(Vector2.Lerp(path.startPosition, path.endPosition, p));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Platformer.Mechanics
|
||||
{
|
||||
/// <summary>
|
||||
/// This component is used to create a patrol path, two points which enemies will move between.
|
||||
/// </summary>
|
||||
public partial class PatrolPath : MonoBehaviour
|
||||
{
|
||||
/// <summary>
|
||||
/// One end of the patrol path.
|
||||
/// </summary>
|
||||
public Vector2 startPosition, endPosition;
|
||||
|
||||
/// <summary>
|
||||
/// Create a Mover instance which is used to move an entity along the path at a certain speed.
|
||||
/// </summary>
|
||||
/// <param name="speed"></param>
|
||||
/// <returns></returns>
|
||||
public Mover CreateMover(float speed = 1) => new Mover(this, speed);
|
||||
|
||||
void Reset()
|
||||
{
|
||||
startPosition = Vector3.left;
|
||||
endPosition = Vector3.right;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// This class allows an audio clip to be played during an animation state.
|
||||
/// </summary>
|
||||
public class PlayAudioClip : StateMachineBehaviour
|
||||
{
|
||||
/// <summary>
|
||||
/// The point in normalized time where the clip should play.
|
||||
/// </summary>
|
||||
public float t = 0.5f;
|
||||
/// <summary>
|
||||
/// If greater than zero, the normalized time will be (normalizedTime % modulus).
|
||||
/// This is used to repeat the audio clip when the animation state loops.
|
||||
/// </summary>
|
||||
public float modulus = 0f;
|
||||
|
||||
/// <summary>
|
||||
/// The audio clip to be played.
|
||||
/// </summary>
|
||||
public AudioClip clip;
|
||||
float last_t = -1f;
|
||||
|
||||
override public void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
|
||||
{
|
||||
var nt = stateInfo.normalizedTime;
|
||||
if (modulus > 0f) nt %= modulus;
|
||||
if (nt >= t && last_t < t)
|
||||
AudioSource.PlayClipAtPoint(clip, animator.transform.position);
|
||||
last_t = nt;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using Platformer.Gameplay;
|
||||
using static Platformer.Core.Simulation;
|
||||
using Platformer.Model;
|
||||
using Platformer.Core;
|
||||
using UnityEngine.InputSystem;
|
||||
|
||||
namespace Platformer.Mechanics
|
||||
{
|
||||
/// <summary>
|
||||
/// This is the main class used to implement control of the player.
|
||||
/// It is a superset of the AnimationController class, but is inlined to allow for any kind of customisation.
|
||||
/// </summary>
|
||||
public class PlayerController : KinematicObject
|
||||
{
|
||||
public AudioClip jumpAudio;
|
||||
public AudioClip respawnAudio;
|
||||
public AudioClip ouchAudio;
|
||||
|
||||
/// <summary>
|
||||
/// Max horizontal speed of the player.
|
||||
/// </summary>
|
||||
public float maxSpeed = 7;
|
||||
/// <summary>
|
||||
/// Initial jump velocity at the start of a jump.
|
||||
/// </summary>
|
||||
public float jumpTakeOffSpeed = 7;
|
||||
|
||||
public JumpState jumpState = JumpState.Grounded;
|
||||
private bool stopJump;
|
||||
/*internal new*/ public Collider2D collider2d;
|
||||
/*internal new*/ public AudioSource audioSource;
|
||||
public Health health;
|
||||
public bool controlEnabled = true;
|
||||
|
||||
bool jump;
|
||||
Vector2 move;
|
||||
SpriteRenderer spriteRenderer;
|
||||
internal Animator animator;
|
||||
readonly PlatformerModel model = Simulation.GetModel<PlatformerModel>();
|
||||
|
||||
private InputAction m_MoveAction;
|
||||
private InputAction m_JumpAction;
|
||||
|
||||
public Bounds Bounds => collider2d.bounds;
|
||||
|
||||
void Awake()
|
||||
{
|
||||
health = GetComponent<Health>();
|
||||
audioSource = GetComponent<AudioSource>();
|
||||
collider2d = GetComponent<Collider2D>();
|
||||
spriteRenderer = GetComponent<SpriteRenderer>();
|
||||
animator = GetComponent<Animator>();
|
||||
|
||||
m_MoveAction = InputSystem.actions.FindAction("Player/Move");
|
||||
m_JumpAction = InputSystem.actions.FindAction("Player/Jump");
|
||||
|
||||
m_MoveAction.Enable();
|
||||
m_JumpAction.Enable();
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
if (controlEnabled)
|
||||
{
|
||||
move.x = m_MoveAction.ReadValue<Vector2>().x;
|
||||
if (jumpState == JumpState.Grounded && m_JumpAction.WasPressedThisFrame())
|
||||
jumpState = JumpState.PrepareToJump;
|
||||
else if (m_JumpAction.WasReleasedThisFrame())
|
||||
{
|
||||
stopJump = true;
|
||||
Schedule<PlayerStopJump>().player = this;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
move.x = 0;
|
||||
}
|
||||
UpdateJumpState();
|
||||
base.Update();
|
||||
}
|
||||
|
||||
void UpdateJumpState()
|
||||
{
|
||||
jump = false;
|
||||
switch (jumpState)
|
||||
{
|
||||
case JumpState.PrepareToJump:
|
||||
jumpState = JumpState.Jumping;
|
||||
jump = true;
|
||||
stopJump = false;
|
||||
break;
|
||||
case JumpState.Jumping:
|
||||
if (!IsGrounded)
|
||||
{
|
||||
Schedule<PlayerJumped>().player = this;
|
||||
jumpState = JumpState.InFlight;
|
||||
}
|
||||
break;
|
||||
case JumpState.InFlight:
|
||||
if (IsGrounded)
|
||||
{
|
||||
Schedule<PlayerLanded>().player = this;
|
||||
jumpState = JumpState.Landed;
|
||||
}
|
||||
break;
|
||||
case JumpState.Landed:
|
||||
jumpState = JumpState.Grounded;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void ComputeVelocity()
|
||||
{
|
||||
if (jump && IsGrounded)
|
||||
{
|
||||
velocity.y = jumpTakeOffSpeed * model.jumpModifier;
|
||||
jump = false;
|
||||
}
|
||||
else if (stopJump)
|
||||
{
|
||||
stopJump = false;
|
||||
if (velocity.y > 0)
|
||||
{
|
||||
velocity.y = velocity.y * model.jumpDeceleration;
|
||||
}
|
||||
}
|
||||
|
||||
if (move.x > 0.01f)
|
||||
spriteRenderer.flipX = false;
|
||||
else if (move.x < -0.01f)
|
||||
spriteRenderer.flipX = true;
|
||||
|
||||
animator.SetBool("grounded", IsGrounded);
|
||||
animator.SetFloat("velocityX", Mathf.Abs(velocity.x) / maxSpeed);
|
||||
|
||||
targetVelocity = move * maxSpeed;
|
||||
}
|
||||
|
||||
public enum JumpState
|
||||
{
|
||||
Grounded,
|
||||
PrepareToJump,
|
||||
Jumping,
|
||||
InFlight,
|
||||
Landed
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Platformer.Mechanics
|
||||
{
|
||||
/// <summary>
|
||||
/// Marks a gameobject as a spawnpoint in a scene.
|
||||
/// </summary>
|
||||
public class SpawnPoint : MonoBehaviour
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Platformer.Mechanics
|
||||
{
|
||||
/// <summary>
|
||||
/// This class animates all token instances in a scene.
|
||||
/// This allows a single update call to animate hundreds of sprite
|
||||
/// animations.
|
||||
/// If the tokens property is empty, it will automatically find and load
|
||||
/// all token instances in the scene at runtime.
|
||||
/// </summary>
|
||||
public class TokenController : MonoBehaviour
|
||||
{
|
||||
[Tooltip("Frames per second at which tokens are animated.")]
|
||||
public float frameRate = 12;
|
||||
[Tooltip("Instances of tokens which are animated. If empty, token instances are found and loaded at runtime.")]
|
||||
public TokenInstance[] tokens;
|
||||
|
||||
float nextFrameTime = 0;
|
||||
|
||||
[ContextMenu("Find All Tokens")]
|
||||
void FindAllTokensInScene()
|
||||
{
|
||||
tokens = UnityEngine.Object.FindObjectsByType<TokenInstance>(FindObjectsInactive.Exclude, FindObjectsSortMode.None);
|
||||
}
|
||||
|
||||
void Awake()
|
||||
{
|
||||
//if tokens are empty, find all instances.
|
||||
//if tokens are not empty, they've been added at editor time.
|
||||
if (tokens.Length == 0)
|
||||
FindAllTokensInScene();
|
||||
//Register all tokens so they can work with this controller.
|
||||
for (var i = 0; i < tokens.Length; i++)
|
||||
{
|
||||
tokens[i].tokenIndex = i;
|
||||
tokens[i].controller = this;
|
||||
}
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
//if it's time for the next frame...
|
||||
if (Time.time - nextFrameTime > (1f / frameRate))
|
||||
{
|
||||
//update all tokens with the next animation frame.
|
||||
for (var i = 0; i < tokens.Length; i++)
|
||||
{
|
||||
var token = tokens[i];
|
||||
//if token is null, it has been disabled and is no longer animated.
|
||||
if (token != null)
|
||||
{
|
||||
token._renderer.sprite = token.sprites[token.frame];
|
||||
if (token.collected && token.frame == token.sprites.Length - 1)
|
||||
{
|
||||
token.gameObject.SetActive(false);
|
||||
tokens[i] = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
token.frame = (token.frame + 1) % token.sprites.Length;
|
||||
}
|
||||
}
|
||||
}
|
||||
//calculate the time of the next frame.
|
||||
nextFrameTime += 1f / frameRate;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
using Platformer.Gameplay;
|
||||
using UnityEngine;
|
||||
using static Platformer.Core.Simulation;
|
||||
|
||||
|
||||
namespace Platformer.Mechanics
|
||||
{
|
||||
/// <summary>
|
||||
/// This class contains the data required for implementing token collection mechanics.
|
||||
/// It does not perform animation of the token, this is handled in a batch by the
|
||||
/// TokenController in the scene.
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(Collider2D))]
|
||||
public class TokenInstance : MonoBehaviour
|
||||
{
|
||||
public AudioClip tokenCollectAudio;
|
||||
[Tooltip("If true, animation will start at a random position in the sequence.")]
|
||||
public bool randomAnimationStartTime = false;
|
||||
[Tooltip("List of frames that make up the animation.")]
|
||||
public Sprite[] idleAnimation, collectedAnimation;
|
||||
|
||||
internal Sprite[] sprites = new Sprite[0];
|
||||
|
||||
internal SpriteRenderer _renderer;
|
||||
|
||||
//unique index which is assigned by the TokenController in a scene.
|
||||
internal int tokenIndex = -1;
|
||||
internal TokenController controller;
|
||||
//active frame in animation, updated by the controller.
|
||||
internal int frame = 0;
|
||||
internal bool collected = false;
|
||||
|
||||
void Awake()
|
||||
{
|
||||
_renderer = GetComponent<SpriteRenderer>();
|
||||
if (randomAnimationStartTime)
|
||||
frame = Random.Range(0, sprites.Length);
|
||||
sprites = idleAnimation;
|
||||
}
|
||||
|
||||
void OnTriggerEnter2D(Collider2D other)
|
||||
{
|
||||
//only exectue OnPlayerEnter if the player collides with this token.
|
||||
var player = other.gameObject.GetComponent<PlayerController>();
|
||||
if (player != null) OnPlayerEnter(player);
|
||||
}
|
||||
|
||||
void OnPlayerEnter(PlayerController player)
|
||||
{
|
||||
if (collected) return;
|
||||
//disable the gameObject and remove it from the controller update list.
|
||||
frame = 0;
|
||||
sprites = collectedAnimation;
|
||||
if (controller != null)
|
||||
collected = true;
|
||||
//send an event into the gameplay system to perform some behaviour.
|
||||
var ev = Schedule<PlayerTokenCollision>();
|
||||
ev.token = this;
|
||||
ev.player = player;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using Platformer.Gameplay;
|
||||
using UnityEngine;
|
||||
using static Platformer.Core.Simulation;
|
||||
|
||||
namespace Platformer.Mechanics
|
||||
{
|
||||
/// <summary>
|
||||
/// Marks a trigger as a VictoryZone, usually used to end the current game level.
|
||||
/// </summary>
|
||||
public class VictoryZone : MonoBehaviour
|
||||
{
|
||||
void OnTriggerEnter2D(Collider2D collider)
|
||||
{
|
||||
var p = collider.gameObject.GetComponent<PlayerController>();
|
||||
if (p != null)
|
||||
{
|
||||
var ev = Schedule<PlayerEnteredVictoryZone>();
|
||||
ev.victoryZone = this;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user