3
\$\begingroup\$

I'm writing a game in C# Monogame, and I've decided to make my project as modular as possible.

GitHub

At first, I've had a single project with a folder that's separated inside to other folders which represent different systems in the game (Items, Abilities, Stats).

I've started by making a .dll project for everything that I've considered as a 'system', i.e - Items, Abilities, Stats, and others that I will soon add, like the player itself (that's just a class right now).

I'm designing the Abilities system based on the answer given on this post.

I'm asking for a code review about the Ability system module, and if you care, about the rest of the game (the MainModule is just remainder of what I've had before I refactored into modularizing everything, so for now there's not much need for whatever's there, unless you see something the could help).

I'd appreciate referencing stuff (or solutions to problems I have) to design patterns, as I'd rather use design patterns to solve problems.

The ability system code:

public interface IBehavior
{
    void ApplyBehavior(IUnit unit);
}

// ---------------------------------------------------- //   

public abstract class ActivatableBehavior : IBehavior
{
    public abstract bool BehaviorImplentation(IUnit destinationPlayer);

    public void ApplyBehavior(IUnit unit)
    {
        BehaviorImplentation(unit);
    }
}

// ---------------------------------------------------- //   

public abstract class LimitedTimeBehavior : IBehavior
{
    public Stopwatch Timer { get; }
    public abstract void ApplyBehavior(IUnit unit);
    public long Duration { get; }

    protected LimitedTimeBehavior(long durationOfBehavior)
    {
        Timer = new Stopwatch();
        Duration = durationOfBehavior;
    }
}

// ---------------------------------------------------- //   

public abstract class TickBasedBehavior : LimitedTimeBehavior
{
    public long TickTime { get; }
    protected abstract void ApplyTick(IUnit unit);


    protected TickBasedBehavior(long durationOfBehavior, long timeBetweenTicks)
        : base(durationOfBehavior)
    {
        TickTime = timeBetweenTicks;
    }

    public override void ApplyBehavior(IUnit unit)
    {
        Thread trd = new Thread(() => ThreadMethod(unit));
        trd.Start();
    }

    private void ThreadMethod(IUnit unit)
    {
        Timer.Start();

        while (Timer.ElapsedMilliseconds < Duration)
        {
            if (Timer.Elapsed.TotalMilliseconds % TickTime == 0)
                ApplyTick(unit);
        }

        Timer.Stop();
    }
}

// ---------------------------------------------------- //   

public abstract class DurationBehavior : LimitedTimeBehavior
{
    protected abstract void Activate(IUnit unit);
    protected abstract void Deactivate(IUnit unit);

    private bool IsActivated { get; set; }

    protected DurationBehavior(long durationOfBehavior)
        : base(durationOfBehavior)
    {
        IsActivated = false;
    }

    public override void ApplyBehavior(IUnit unit)
    {
        if (IsActivated) return;
        Thread trd = new Thread(() => ThreadMethod(unit));
        trd.Start();
        IsActivated = true;
    }

    private void ThreadMethod(IUnit unit)
    {
        Timer.Start();
        Activate(unit);
        while (Timer.Elapsed.TotalMilliseconds < Duration) { }
        Timer.Reset();
        Deactivate(unit);
        IsActivated = false;
    }
}
\$\endgroup\$
7
\$\begingroup\$

That is really not how you should be using a Stopwatch...

E.g. this

while (Timer.Elapsed.TotalMilliseconds < Duration) { }

You're needlessly using CPU cycles while you're waiting. That's not good! You know exactly when you want to do something so you should scheduled something rather than do pretend work. A simple example could be:

private void ThreadMethod(IUnit unit)
{
    Activate(unit);
    Task.Delay(Duration).ContinueWith(_ => Deactivate(unit));
}

This isn't safe either:

while (Timer.ElapsedMilliseconds < Duration)
{
    if (Timer.Elapsed.TotalMilliseconds % TickTime == 0)
        ApplyTick(unit);
}

You're very unlikely to actually get true on that if statement meaning you're unlikely to call ApplyTick and almost certainly won't call it the number of times you want to! That's because a Tick is a really, really small measure of time... I want to say 1/10000 of a millisecond.

Edit - I misread your if statement - I thought you were using Ticks (due to the TickTime field. It's still not a great idea but less serious than the above implies.

You shouldn't need a stopwatch at all for this sort of thing.


You should use the highest level of abstraction you can. A double can be anything (e.g. hours, minutes, seconds...) - a timespan is a period of time, much more suitable for your LimitedTimeBehavior:

public abstract class LimitedTimeBehavior : IBehavior
{
    public abstract void ApplyBehavior(IUnit unit);

    public TimeSpan Duration { get; }

    protected LimitedTimeBehavior(TimeSpan duration)
    {
        Duration = duration;
    }
}

You said you're interested in design patterns so I though I'd draw your attention to one you're nearly using:

public abstract class ActivatableBehavior : IBehavior
{
    public abstract bool BehaviorImplentation(IUnit destinationPlayer);

    public void ApplyBehavior(IUnit unit)
    {
        BehaviorImplentation(unit);
    }
}

That's the Template Method pattern but it's more normal to call multiple methods within the template method.

public abstract class ActivatableBehavior : IBehavior
{
    public abstract void BehaviorImplementation(IUnit destinationPlayer);

    public abstract bool CanApplyBehvaiorTo(IUnit destinationPlayer);

    public void ApplyBehavior(IUnit unit)
    {
        if (CanApplyBehvaiorTo(unit))
        {
            BehaviorImplementation(unit);
        }
    }
}

Note that I've fixed your typo (Implentation -> Implementation) and have removed the bool return type that was never used.

\$\endgroup\$
  • \$\begingroup\$ Hey man, awesome answer, thanks for that. | 1. The Task.Delay().ContinueWith, great stuff, thanks a lot. | 2. if (Timer.Elapsed.TotalMilliseconds % TickTime == 0) works pretty well for me, I'll make sure to use the task.delay here too because it serves the same purpose | 3. Great idea on CanApplyBehaviorTo, i'll be sure to use that. | 4. the bool return type was is already fixed on my local machine, not committed yet probably. And thanks for showing me the typo. \$\endgroup\$ – Giora Guttsait Sep 19 '15 at 19:40
  • \$\begingroup\$ Github now holds the updated code of all behavior related classes. Thank you for your answer, really appreciated \$\endgroup\$ – Giora Guttsait Sep 19 '15 at 20:23
  • \$\begingroup\$ @GioraGuttsait - You're welcome! I misread the if statement - I thought you were using Ticks not milliseconds. \$\endgroup\$ – RobH Sep 20 '15 at 7:48

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.