Using the CSCore library, I wrote the code for playing an mp3 file in the class BGM in a seperate file called BGM.cs and the method for playback is BGM.Play("file directory");, which is called in the Form. But somehow I can't manage to get any sound out of it. I've already checked volume, codec and output, and I can't think of anything else that might cause this problem.
This is the code of the class file:
public class BGM
{
public static void Play(string file)
{
using (IWaveSource soundSource = GetSoundSource(file))
{
using (ISoundOut soundOut = GetSoundOut())
{
soundOut.Initialize(soundSource);
soundOut.Volume = 0.8f;
soundOut.Play();
}
}
}
private static ISoundOut GetSoundOut()
{
if (WasapiOut.IsSupportedOnCurrentPlatform)
return new WasapiOut();
else
return new DirectSoundOut();
}
private static IWaveSource GetSoundSource(string file)
{
return CodecFactory.Instance.GetCodec(file);
}
There are actually a couple reasons why your mp3 isn't playing.
The first reason is you haven't specified a device for the sound to play on. The code below gets the first device that can render sound, but that won't always be correct if the user has multiple devices attached to their computer. You'll have to handle that appropriately. The device has to be set on the WasapiOut object.
The second reason is your use of using statements in your Play method. While it's always a good idea to clean up objects that implement IDisposable, you can't always do so immediately. In this case, soundOut.Play() is not a blocking method, which meant that control was exiting the method immediately, causing Dispose() to be called on soundOut and soundSource. This meant that the sound would effectively never be played (maybe it would start for a short moment, but not enough to really hear it). Essentially, you need to hold onto the references and only dispose of them once playback is complete.
Have a look at the AudioPlayerSample for an idea on how to implement a complete solution. My code should get you started.
void Main()
{
using(var player = new BGM(#"D:\Test.mp3"))
{
player.Play();
// TODO: Need to wait here in order for playback to complete
// Otherwise, you need to hold onto the player reference and dispose of it later
Console.ReadLine();
}
}
public class BGM : IDisposable
{
private bool _isDisposed = false;
private ISoundOut _soundOut;
private IWaveSource _soundSource;
public BGM(string file)
{
_soundSource = CodecFactory.Instance.GetCodec(file);
_soundOut = GetSoundOut();
_soundOut.Initialize(_soundSource);
}
public void Play()
{
if(_soundOut != null)
{
_soundOut.Volume = 0.8f;
_soundOut.Play();
}
}
public void Stop()
{
if(_soundOut != null)
{
_soundOut.Stop();
}
}
private static ISoundOut GetSoundOut()
{
if (WasapiOut.IsSupportedOnCurrentPlatform)
{
return new WasapiOut
{
Device = GetDevice()
};
}
return new DirectSoundOut();
}
private static IWaveSource GetSoundSource(string file)
{
return CodecFactory.Instance.GetCodec(file);
}
public static MMDevice GetDevice()
{
using(var mmdeviceEnumerator = new MMDeviceEnumerator())
{
using(var mmdeviceCollection = mmdeviceEnumerator.EnumAudioEndpoints(DataFlow.Render, DeviceState.Active))
{
// This uses the first device, but that isn't what you necessarily want
return mmdeviceCollection.First();
}
}
}
protected virtual void Dispose(bool disposing)
{
if (!_isDisposed)
{
if (disposing)
{
if(_soundOut != null)
{
_soundOut.Dispose();
}
if(_soundSource != null)
{
_soundSource.Dispose();
}
}
_isDisposed = true;
}
}
public void Dispose()
{
Dispose(true);
}
}
Related
The Idea is, that my Data get loaded, asynchron, when my app startet. Because the user normaly need some seconds to interact and in this time, i should get my data. (For understanding reasons, it will be a book app, with some books)
To do this, in loadingprofile i open a method on a static class to start loading.
In the Screen where i want to show my data, i check with a method on the static class, if the data are there ( and wait if theire not).
So in the Profil loading screen i did this:
public class LoadProfiles : MonoBehaviour
{
private void Awake()
{
if (!LoadingData.dataLoaded)
{
LoadingData.LoadData();
}
}
void Start()
{
//Loading Profile Dataa
}
}
Then when the User click on a profile, i open the next scene "Bookshelf" and a game object in it has the following script attachted to read the data (the book categories). so i open the wait on data method, that shoud return, when the data are arrived.
public class PopulateShelf : MonoBehaviour
{
private void Start()
{
LoadingData.WaitOnData();
LoadingData.Categories.ForEach(c => {
//create categories
});
}
}
The static loading class looks like the following
public class LoadingData : MonoBehaviour
{
public static List<CategoryDTO> Categories;
public static IEnumerator CategoriesRequest;
public static bool dataLoaded = false;
public static void LoadData()
{
CategoriesRequest = LoadCategories();
dataLoaded = true;
}
public static void WaitOnData()
{
if (!dataLoaded) LoadData();
while (CategoriesRequest.MoveNext())
{
//Log wait on categories
}
}
public static IEnumerator LoadCategories()
{
UnityWebRequest request = UnityWebRequest.Get(Constants.apiURL + Constants.apiURLCategories);
yield return request.SendWebRequest();
while (!request.isDone)
{
yield return true;
}
if (request.result == UnityWebRequest.Result.ConnectionError)
{
//Log error
}
else
{
Categories = JsonConvert.DeserializeObject<List<CategoryDTO>>(request.downloadHandler.text);
Categories.Sort((x, y) => x.Order.CompareTo(y.Order));
}
}
}
So, the problem is that the on the loging screen, it opens the LoadingData.LoadData Method (but it do not start loading).
When im in the Bookshelf Scene, and open LoadingData.WaitOnData it starts loading the data and the User have to wait.
I also tried it with StartCoroutines. I refactored the LoadingData class, to a non static class. Startet the Coroutings in the LoadData Method.
First it looked like it worked, the data was loaded in the profileScene
Because of it was not static, i had to pass somehow the Object. To do this i created in a static script a Constants for a static LoadingData script variable.
In the Profile Scene i attached it and loaded the data. When i wanted to read it in the BookShefScene, i got a null refrence in my static class...
Here would be the class with coroutines
public class LoadingData : MonoBehaviour
{
public Dictionary<int, BookDTO> Books = new Dictionary<int, BookDTO>();
public List<CategoryDTO> Categories;
public bool dataLoaded = false;
public void LoadData()
{
StartCoroutine(LoadCategories());
StartCoroutine(LoadBooks());
dataLoaded = true;
}
public void WaitOnData()
{
if (!dataLoaded) LoadData();
while (categorieLoaded)
{
//log waiting
}
}
public IEnumerator LoadCategories()
{
UnityWebRequest request = UnityWebRequest.Get(Constants.apiURL + Constants.apiURLCategories);
yield return request.SendWebRequest();
while (!request.isDone)
{
yield return true;
}
if (request.result == UnityWebRequest.Result.ConnectionError)
{
//Log Error
}
else
{
Categories = JsonConvert.DeserializeObject<List<CategoryDTO>>(request.downloadHandler.text);
Categories.Sort((x, y) => x.Order.CompareTo(y.Order));
}
}
}
in profile loading screen, to attache my script to the static variable, i said something like this:
Constants.loadingData = this.GetComponent<LoadingData>();
Constants.loadingData.LoadData();
and in the bookshelf i said
Constants.loadingData.WaitOnData();
Constants.loadingData.Categories();
I checked with the Debuger, in the Profile Scene the Constants.loadingData was created and referenced.
In the Bookshelf Scene, i dont saw the referenced object (it was null)...
can anyoune help me with one of this solution? or have a third solution?
I think your condition should be
if(!dataLoaded) LoadData();
Same for the categorieLoaded, maybe the name is misleading and you are using them correctly (?)
Is the GameObject stored in Constants.loadedData set as dontDestroyOnLoad? Maybe when you change scene it gets destroied and set to null.
Also I would suggest to have a better way of waiting for the data to load, if it takes seconds the user will not be able to interract with the game since the while loop will freeze the entire window, you can make your own method that checks every frame if the data got loaded.
private IEnumerable WaitForData()
{
while(!Constants.loadedData.Loaded) return null;
// populate the bookshelf
// foreach(Book book in Constants.Books)
}
You need to call LoadData() somewhere before calling this, you could setup a loading screen while waiting aswell to make players understand.
I am trying to record video from a webcam using OpenCvSharp
I can already record the video using the code below but the resulting .mp4 file plays way to fast (e.g. i record for 5 seconds and the result isn't even one second long).
I already played with the delay in AddCameraFrameToRecordingThread but to no avail
What can possibly be the problem? Or what other library can I use to record a video from webcam?
namespace BlackBears.Recording
{
using System;
using System.Drawing;
using System.Threading;
using OpenCvSharp;
using OpenCvSharp.Extensions;
using Size = OpenCvSharp.Size;
public class Recorder : IDisposable
{
private readonly VideoCaptureAPIs _videoCaptureApi = VideoCaptureAPIs.DSHOW;
private readonly ManualResetEventSlim _writerReset = new(false);
private readonly VideoCapture _videoCapture;
private VideoWriter _videoWriter;
private Thread _writerThread;
private bool IsVideoCaptureValid => _videoCapture is not null && _videoCapture.IsOpened();
public Recorder(int deviceIndex, int frameWidth, int frameHeight, double fps)
{
_videoCapture = VideoCapture.FromCamera(deviceIndex, _videoCaptureApi);
_videoCapture.Open(deviceIndex, _videoCaptureApi);
_videoCapture.FrameWidth = frameWidth;
_videoCapture.FrameHeight = frameHeight;
_videoCapture.Fps = fps;
}
/// <inheritdoc />
public void Dispose()
{
GC.SuppressFinalize(this);
Dispose(true);
}
~Recorder()
{
Dispose(false);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
StopRecording();
_videoCapture?.Release();
_videoCapture?.Dispose();
}
}
public void StartRecording(string path)
{
if (_writerThread is not null)
return;
if (!IsVideoCaptureValid)
ThrowHelper.ThrowVideoCaptureNotReadyException();
_videoWriter = new VideoWriter(path, FourCC.XVID, _videoCapture.Fps, new Size(_videoCapture.FrameWidth, _videoCapture.FrameHeight));
_writerReset.Reset();
_writerThread = new Thread(AddCameraFrameToRecordingThread);
_writerThread.Start();
}
public void StopRecording()
{
if (_writerThread is not null)
{
_writerReset.Set();
_writerThread.Join();
_writerThread = null;
_writerReset.Reset();
}
_videoWriter?.Release();
_videoWriter?.Dispose();
_videoWriter = null;
}
private void AddCameraFrameToRecordingThread()
{
var waitTimeBetweenFrames = (int)(1_000 / _videoCapture.Fps);
using var frame = new Mat();
while (!_writerReset.Wait(waitTimeBetweenFrames))
{
if (!_videoCapture.Read(frame))
return;
_videoWriter.Write(frame);
}
}
}
}
I found a solution myself after a lot more playing around.
A single thread to capture the frames and write them was not enough.
I now created two threads, one that captures the frames from the camera and one that writes them. To have the correct timing in the resulting file the delay the write creates has to be taken into account.
I ended up with the following two functions that run in separate threads:
private void CaptureFrameLoop()
{
while (!_threadStopEvent.Wait(0))
{
_videoCapture.Read(_capturedFrame);
}
}
private void AddCameraFrameToRecordingThread()
{
var waitTimeBetweenFrames = 1_000 / _videoCapture.Fps;
var lastWrite = DateTime.Now;
while (!_threadStopEvent.Wait(0))
{
if (DateTime.Now.Subtract(lastWrite).TotalMilliseconds < waitTimeBetweenFrames)
continue;
lastWrite = DateTime.Now;
_videoWriter.Write(_capturedFrame);
}
}
I've been building out a service that processes files using a Queue<string> object to manage the items.
public partial class BasicQueueService : ServiceBase
{
private readonly EventWaitHandle completeHandle =
new EventWaitHandle(false, EventResetMode.ManualReset, "ThreadCompleters");
public BasicQueueService()
{
QueueManager = new Queue<string>();
}
public bool Stopping { get; set; }
private Queue<string> QueueManager { get; }
protected override void OnStart(string[] args)
{
Stopping = false;
ProcessFiles();
}
protected override void OnStop()
{
Stopping = true;
}
private void ProcessFiles()
{
while (!Stopping)
{
var count = QueueManager.Count;
for (var i = 0; i < count; i++)
{
//Check the Stopping Variable again.
if (Stopping) break;
var fileName = QueueManager.Dequeue();
if (string.IsNullOrWhiteSpace(fileName) || !File.Exists(fileName))
continue;
Console.WriteLine($"Processing {fileName}");
Task.Run(() =>
{
DoWork(fileName);
})
.ContinueWith(ThreadComplete);
}
if (Stopping) continue;
Console.WriteLine("Waiting for thread to finish, or 1 minute.");
completeHandle.WaitOne(new TimeSpan(0, 0, 15));
completeHandle.Reset();
}
}
partial void DoWork(string fileName);
private void ThreadComplete(Task task)
{
completeHandle.Set();
}
public void AddToQueue(string file)
{
//Called by FileWatcher/Manual classes, not included for brevity.
lock (QueueManager)
{
if (QueueManager.Contains(file)) return;
QueueManager.Enqueue(file);
}
}
}
Whilst researching how to limit the number of threads on this (I've tried a manual class with an incrementing int, but there's an issue where it doesn't decrement properly in my code), I came across TPL DataFlow, which seems like its a better fit for what I'm trying to achieve - specifically, it allows me to let the framework handle threading/queueing, etc.
This is now my service:
public partial class BasicDataFlowService : ServiceBase
{
private readonly ActionBlock<string> workerBlock;
public BasicDataFlowService()
{
workerBlock = new ActionBlock<string>(file => DoWork(file), new ExecutionDataflowBlockOptions()
{
MaxDegreeOfParallelism = 32
});
}
public bool Stopping { get; set; }
protected override void OnStart(string[] args)
{
Stopping = false;
}
protected override void OnStop()
{
Stopping = true;
}
partial void DoWork(string fileName);
private void AddToDataFlow(string file)
{
workerBlock.Post(file);
}
}
This works well. However, I want to ensure that a file is only ever added to the TPL DataFlow once. With the Queue, I can check that using .Contains(). Is there a mechanism that I can use for TPL DataFlow?
Your solution with Queue works only if file goes into your service twice in a small period of time. If it came again in, say, few hours, queue will not contain it, as you Dequeue it from there.
If this solution is expected, then you may use a MemoryCache to store file paths being already handled, like this:
using System.Runtime.Caching;
private static object _lock = new object();
private void AddToDataFlow(string file)
{
lock (_lock)
{
if (MemoryCache.Default.Contains(file))
{
return;
}
// no matter what to put into the cache
MemoryCache.Default[file] = true;
// we can now exit the lock
}
workerBlock.Post(file);
}
However, if your application must run for a long time (which service is intended to do), you'll eventually run out of memory. In that case you probably need to store your file paths in database or something, so even after restarting the service your code will restore the state.
You can check it inside of DoWork.
You have to save in Hash already works items and check current filename doesn't exist in hash.
I trying to allow people to write to NFC tags using my app, so that my app gets launched with a custom parameter. I want to be able to reprogram NFC tags which already have data on them.
I am using the following code but the problem is, that WP always recognizes the action which is already on the NFC tag and interrupts because it wants to launch the NFC tag action which was written anytime before.
How can I tell the OS to stop triggering the action of the tag so that I can immediately rewrite it?
public enum NfcHelperState
{
Initializing,
Waiting,
Ready,
Writing,
Finished,
Error,
NoDeviceFound
}
public class NfcHelper
{
private NfcHelperState _state = NfcHelperState.Initializing;
public NfcHelperState State
{
get { return _state; }
}
private ProximityDevice _nfcDevice;
private long _subscriptionId;
public NfcHelper()
{
Init();
}
public void Init()
{
UpdateState();
_nfcDevice = ProximityDevice.GetDefault();
if (_nfcDevice == null)
{
UpdateState(NfcHelperState.NoDeviceFound);
return;
}
UpdateState(NfcHelperState.Waiting);
}
private void UpdateState(NfcHelperState? state = null)
{
if (state.HasValue)
{
_state = state.Value;
}
if (OnStatusMessageChanged != null)
{
OnStatusMessageChanged(this, _state);
}
}
public void WriteToTag()
{
UpdateState(NfcHelperState.Ready);
_subscriptionId = _nfcDevice.SubscribeForMessage("WriteableTag", WriteableTagDetected);
}
private void WriteableTagDetected(ProximityDevice sender, ProximityMessage message)
{
UpdateState(NfcHelperState.Writing);
try
{
var str = "action=my_custom_action";
str += "\tWindowsPhone\t";
str += CurrentApp.AppId;
_nfcDevice.PublishBinaryMessage("LaunchApp:WriteTag", GetBufferFromString(str),
WriteToTagComplete);
}
catch (Exception e)
{
UpdateState(NfcHelperState.Error);
StopWaitingForTag();
}
}
private void WriteToTagComplete(ProximityDevice sender, long messageId)
{
sender.StopPublishingMessage(messageId);
UpdateState(NfcHelperState.Finished);
StopWaitingForTag();
}
private void StopWaitingForTag()
{
_nfcDevice.StopSubscribingForMessage(_subscriptionId);
}
private static IBuffer GetBufferFromString(string str)
{
using (var dw = new DataWriter())
{
dw.UnicodeEncoding = Windows.Storage.Streams.UnicodeEncoding.Utf16LE;
dw.WriteString(str);
return dw.DetachBuffer();
}
}
public delegate void NfcStatusMessageChangedHandler(object myObject, NfcHelperState newState);
public event NfcStatusMessageChangedHandler OnStatusMessageChanged;
}
WriteToTag is called when a button in my app is tapped and the app waits for a writable tag. If a writable tag is recognized, WriteableTagDetected gets called and immediately starts the writing process. However, this is interrupted by the WP dialog which asks whether to perform the NFC action or not. After writing, WriteToTagComplete should be called, where StopWaitingForTag gets called and ends the write process.
I hope you guys can help me :)
Turns out I thought the wrong way. I didn't need to wait for a tag to arrive in order to rewrite it. In fact, there's no need to do _nfcDevice.SubscribeForMessage("WriteableTag", WriteableTagDetected); before writing. Just start using PublishBinaryMessage and it will write to the tag once it arrives at the device.
My final code looks like the following:
public enum NfcHelperState
{
Initializing,
Ready,
WaitingForWriting,
FinishedWriting,
ErrorWriting,
NoDeviceFound
}
public class NfcHelper
{
private NfcHelperState _state = NfcHelperState.Initializing;
public NfcHelperState State
{
get { return _state; }
}
private ProximityDevice _nfcDevice;
private long? _writingMessageId;
public NfcHelper()
{
Init();
}
public void Init()
{
UpdateState();
_nfcDevice = ProximityDevice.GetDefault();
if (_nfcDevice == null)
{
UpdateState(NfcHelperState.NoDeviceFound);
return;
}
UpdateState(NfcHelperState.Ready);
}
private void UpdateState(NfcHelperState? state = null)
{
if (state.HasValue)
{
_state = state.Value;
}
if (OnStatusMessageChanged != null)
{
OnStatusMessageChanged(this, _state);
}
}
public void WriteToTag()
{
StopWritingMessage();
UpdateState(NfcHelperState.WaitingForWriting);
try
{
var str = new StringBuilder();
str.Append("action=my_custom_action");
str.Append("\tWindowsPhone\t{");
str.Append(CurrentApp.AppId);
str.Append("}");
_writingMessageId = _nfcDevice.PublishBinaryMessage("LaunchApp:WriteTag", GetBufferFromString(str.ToString()),
WriteToTagComplete);
}
catch
{
UpdateState(NfcHelperState.ErrorWriting);
StopWritingMessage();
}
}
private void WriteToTagComplete(ProximityDevice sender, long messageId)
{
UpdateState(NfcHelperState.FinishedWriting);
StopWritingMessage();
}
private void StopWritingMessage()
{
if (_writingMessageId.HasValue)
{
_nfcDevice.StopPublishingMessage(_writingMessageId.Value);
_writingMessageId = null;
}
}
private static IBuffer GetBufferFromString(string str)
{
using (var dw = new DataWriter())
{
dw.UnicodeEncoding = Windows.Storage.Streams.UnicodeEncoding.Utf16LE;
dw.WriteString(str);
return dw.DetachBuffer();
}
}
public delegate void NfcStatusMessageChangedHandler(object myObject, NfcHelperState newState);
public event NfcStatusMessageChangedHandler OnStatusMessageChanged;
}
My son is writing a simple RPG game that has a number of non-player characters (aka NPC's). Each NPC has an associated "script" that controls its behaviour. We were going to use a mini custom script language to write these behaviours but I'm now wondering if this would be better done in C#5/Async.
Taking a really simple example, suppose one of the NPC's just walks between two points I'm thinking it would be nice to write something like this:
while (true)
{
await WalkTo(100,100);
await WalkTo(200,200);
}
The WalkTo method would be an async method that handles everything to do with walking between the two points and does this over a number of frames from the game loop. It's not a blocking method that can be off-loaded to a background thread.
And this is where I'm stuck... I haven't been able to find any examples using async/await in this manner, but it seems it would be perfect for it.
Ideas?
Here's some very rough pseudo code for what I'd like to do:
class NpcBase
{
// Called from game loop
public void onUpdate(double elapsedTime)
{
// Move the NPC
.
.
.
// Arrived at destination?
if (Arrived)
{
// How do I trigger that the task is finished?
_currentTask.MarkComplete();
}
}
// Async method called by NPC "script"
public async Task WalkTo(int x, int y)
{
// Store new target location
// return a task object that will be "triggered" when the walk is finished
_currentTask = <something??>
return _currentTask;
}
Task _currentTask;
}
Okay, it sounds like one option would be to have a TaskCompletionSource for each frame of the game. You can then await the Task from WalkTo, and set the result in OnUpdate:
private TaskCompletionSource<double> currentFrameSource;
// Called from game loop
public void OnUpdate(double elapsedTime)
{
...
var previousFrameSource = currentFrameSource;
currentFrameSource = new TaskCompletionSource<double>();
// This will trigger all the continuations...
previousFrameSource.SetResult(elapsedTime);
}
// Async method called by NPC "script"
public async Task WalkTo(int x, int y)
{
// Store new target location
while (/* we're not there yet */)
{
double currentTime = await currentFrameSource.Task;
// Move
}
}
I'm not sure how efficient this will be, admittedly... but it should work.
I think I've figured it out in a simple test program
Firstly, I've got a base class for the NPC's like this:
EDIT: Updated NpcBase to use TaskCompletionSource:
public class NpcBase
{
// Derived classes to call this when starting an async operation
public Task BeginTask()
{
// Task already running?
if (_tcs!= null)
{
throw new InvalidOperationException("busy");
}
_tcs = new TaskCompletionSource<int>();
return _tcs.Task;
}
TaskCompletionSource<int> _tcs;
// Derived class calls this when async operation complete
public void EndTask()
{
if (_tcs != null)
{
var temp = _tcs;
_tcs = null;
temp.SetResult(0);
}
}
// Is this NPC currently busy?
public bool IsBusy
{
get
{
return _tcs != null;
}
}
}
For reference, here's the old version of NpcBase with custom IAsyncResult implementation instead of TaskCompletionSource:
// DONT USE THIS, OLD VERSION FOR REFERENCE ONLY
public class NpcBase
{
// Derived classes to call this when starting an async operation
public Task BeginTask()
{
// Task already running?
if (_result != null)
{
throw new InvalidOperationException("busy");
}
// Create the async Task
return Task.Factory.FromAsync(
// begin method
(ac, o) =>
{
return _result = new Result(ac, o);
},
// End method
(r) =>
{
},
// State object
null
);
}
// Derived class calls this when async operation complete
public void EndTask()
{
if (_result != null)
{
var temp = _result;
_result = null;
temp.Finish();
}
}
// Is this NPC currently busy?
public bool IsBusy
{
get
{
return _result != null;
}
}
// Result object for the current task
private Result _result;
// Simple AsyncResult class that stores the callback and the state object
class Result : IAsyncResult
{
public Result(AsyncCallback callback, object AsyncState)
{
_callback = callback;
_state = AsyncState;
}
private AsyncCallback _callback;
private object _state;
public object AsyncState
{
get { return _state; ; }
}
public System.Threading.WaitHandle AsyncWaitHandle
{
get { throw new NotImplementedException(); }
}
public bool CompletedSynchronously
{
get { return false; }
}
public bool IsCompleted
{
get { return _finished; }
}
public void Finish()
{
_finished = true;
if (_callback != null)
_callback(this);
}
bool _finished;
}
}
Next, I've got a simple "NPC" that moves in one dimension. When a moveTo operation starts it calls BeginTask in the NpcBase. When arrived at the destination, it calls EndTask().
public class NpcTest : NpcBase
{
public NpcTest()
{
_position = 0;
_target = 0;
}
// Async operation to count
public Task MoveTo(int newPosition)
{
// Store new target
_target = newPosition;
return BeginTask();
}
public int Position
{
get
{
return _position;
}
}
public void onFrame()
{
if (_position == _target)
{
EndTask();
}
else if (_position < _target)
{
_position++;
}
else
{
_position--;
}
}
private int _position;
private int _target;
}
And finally, a simple WinForms app to drive it. It consists of a button and two labels. Clicking the button starts both NPC and their position is displayed on the labels.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void onButtonClick(object sender, EventArgs e)
{
RunNpc1();
RunNpc2();
}
public async void RunNpc1()
{
while (true)
{
await _npc1.MoveTo(20);
await _npc1.MoveTo(10);
}
}
public async void RunNpc2()
{
while (true)
{
await _npc2.MoveTo(80);
await _npc2.MoveTo(70);
}
}
NpcTest _npc1 = new NpcTest();
NpcTest _npc2 = new NpcTest();
private void timer1_Tick(object sender, EventArgs e)
{
_npc1.onFrame();
_npc2.onFrame();
label1.Text = _npc1.Position.ToString();
label2.Text = _npc2.Position.ToString();
}
}
And it works, all seems to be running on the main UI thread... which is what I wanted.
Of course it needs to be fixed to handle cancelling of operations, exceptions etc... but the basic idea is there.