Skip to content

Instantly share code, notes, and snippets.

@bengsfort
Last active July 6, 2017 03:50
Show Gist options
  • Save bengsfort/81024aa7cb1ea7cc0ceec7b45bb2c08b to your computer and use it in GitHub Desktop.
Save bengsfort/81024aa7cb1ea7cc0ceec7b45bb2c08b to your computer and use it in GitHub Desktop.
Full implementation of an animated section end trigger using ECS
using System;
using Bengsfort.Components.Core;
using UnityEngine;
namespace Bengsfort.Components
{
// This component handles the visual rendering of the section end component.
[Serializable]
public class SectionEndComponent : BaseComponent
{
public SpriteRenderer renderer;
public Sprite unlockedSprite;
public Sprite lockedSprite;
public float spriteTick = 0.1f;
// These are state related and handled at run time, so we don't need to serialize them.
[NonSerialized]
public float lastTick;
[NonSerialized]
public bool unlockSpriteActive;
}
}
using UnityEngine;
using Bengsfort.Components;
using Bengsfort.Systems;
namespace Bengsfort.Entities
{
// MonoBehaviour's are treated as entities since they can subscribe to the built-in
// Unity API messages. The components attached to the entities are serializable, so
// you can edit/tweak the default values for them in the editor like any other
// serializable properties.
[RequireComponent(typeof(CircleCollider2D))]
public class SectionEndEntity : MonoBehaviour
{
// The components relevant to this entity. These can be tweaked in-editor.
public TimedUnlockComponent timedUnlockComponent;
public SectionEndComponent sectionEndComponent;
// The systems relevant to this entity.
private SectionEndSystem m_SectionEndSystem;
// Instantiate any required systems on Start.
void Start()
{
m_SectionEndSystem = new SectionEndSystem(timedUnlockComponent, sectionEndComponent);
}
// This particular system should Tick every Update.
void Update()
{
m_SectionEndSystem.Tick();
}
// This system depends on a trigger, so initialize that when needed.
void OnTriggerEnter2D(Collider2D collision)
{
if (timedUnlockComponent.unlocked)
return;
if (collision.CompareTag("Player"))
m_SectionEndSystem.StartTimer();
}
// Reset it when the player leaves the collider.
void OnTriggerExit2D(Collider2D collision)
{
if (timedUnlockComponent.unlocked)
return;
if (collision.CompareTag("Player"))
m_SectionEndSystem.ResetTimer();
}
}
}
using Bengsfort.Components;
using UnityEngine;
namespace Bengsfort.Systems
{
// Technically this *should/could* just inherit from the base system, but I figured
// it didn't hurt to just use inheritence here.
public class SectionEndSystem : TimedUnlockSystem
{
// Cached component state.
private SectionEndComponent m_SectionEnd;
// When the entity is 'unlocked'...
protected override void Unlock()
{
// Let the timer do what it needs to do.
base.Unlock();
// Increment the static stage state's active section property.
StageComponent.Instance.activeSection += 1;
StageComponent.Instance.sectionChanged = true;
// Reset the visual apperance of the trigger.
m_SectionEnd.lastTick = -1.0f;
m_SectionEnd.renderer.sprite = m_SectionEnd.unlockedSprite;
m_SectionEnd.unlockSpriteActive = true;
}
// Start our timer and also begin updating the visuals for the entity
public override void StartTimer()
{
base.StartTimer();
// Default to unlocked sprite to give player immediate feedback
m_SectionEnd.lastTick = Time.time;
m_SectionEnd.renderer.sprite = m_SectionEnd.unlockedSprite;
m_SectionEnd.unlockSpriteActive = true;
}
// Reset the timer when the player doesn't successfully unlock the timer.
public override void ResetTimer()
{
// Let the timer do what it needs to do...
base.ResetTimer();
// Then reset the visuals of the entity.
m_SectionEnd.lastTick = -1.0f;
m_SectionEnd.renderer.sprite = m_SectionEnd.lockedSprite;
m_SectionEnd.unlockSpriteActive = false;
}
public override void Tick()
{
base.Tick();
if (m_TimedUnlock.beingUnlocked)
{
// If the entity is being unlocked and the sprite flash duration has elapsed
if (m_SectionEnd.lastTick + m_SectionEnd.spriteTick <= Time.time)
{
// Swap the sprite
m_SectionEnd.lastTick = Time.time;
m_SectionEnd.renderer.sprite = m_SectionEnd.unlockSpriteActive
? m_SectionEnd.lockedSprite
: m_SectionEnd.unlockedSprite;
m_SectionEnd.unlockSpriteActive = !m_SectionEnd.unlockSpriteActive;
}
}
}
// Cache the relevant components
public SectionEndSystem(TimedUnlockComponent timer, SectionEndComponent sectionEnd)
{
m_TimedUnlock = timer;
m_SectionEnd = sectionEnd;
}
}
}
using System;
using Bengsfort.Components.Core;
namespace Bengsfort.Components
{
// This component deals with unlocking something via a specified-length timer.
[Serializable]
public class TimedUnlockComponent : BaseComponent
{
public float unlockTimer = 1.5f;
// These are internal/runtime state related, so don't show them in the inspector.
[NonSerialized]
public float unlockStarted = -1.0f;
[NonSerialized]
public bool beingUnlocked;
[NonSerialized]
public bool unlocked;
}
}
using System;
using Bengsfort.Systems.Core;
using Bengsfort.Components;
using UnityEngine;
namespace Bengsfort.Systems
{
// This system deals with the logic of starting and maintaining an unlock timer.
public class TimedUnlockSystem : BaseSystem
{
// Cached timer component.
protected TimedUnlockComponent m_TimedUnlock;
// When the player has unlocked the entity, we can set our state to unlocked and reset.
protected virtual void Unlock()
{
m_TimedUnlock.unlocked = true;
ResetTimer();
}
// Resets the internal state to allow for starting the timer up again when needed.
public virtual void ResetTimer()
{
m_TimedUnlock.beingUnlocked = false;
m_TimedUnlock.unlockStarted = -1.0f;
}
// Set the timer state to start counting down next frame.
public virtual void StartTimer()
{
m_TimedUnlock.beingUnlocked = true;
m_TimedUnlock.unlockStarted = Time.time;
}
public override void Tick()
{
// If we're being unlocked...
if (m_TimedUnlock.beingUnlocked)
{
// Check the elapsed time
var timeElapsed = m_TimedUnlock.unlockStarted + m_TimedUnlock.unlockTimer;
if (timeElapsed <= Time.time)
Unlock();
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment