How do I make timer change a sprite every second without using code that's redundant? - image

I'm not entirely sure how to phrase question so sorry if this is confusing. Anyways for context I'm making a sort of minesweeper type of game in unity and one of the things the original game had was a timer. Here it is. I want to copy that sort of thing, and while I do have code that works, it's honestly kind of redundant here's what I have .
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class Timer : MonoBehaviour
{
public float timer = 0;
public bool isStop = false;
public Image scoreCount;
public Sprite[] numbersprite;
// Start is called before the first frame update
void Start()
{
timer = 0;
isStop = true;
}
Ignore all the stuff on the top.
// Update is called once per frame
void Update()
{
if(!isStop)
{
timer += Time.deltaTime;
if(timer >= 1f)
{
scoreCount.sprite = numbersprite[1];
}
if(timer >= 2f)
{
scoreCount.sprite = numbersprite[2];
}
if(timer >= 3f)
{
scoreCount.sprite = numbersprite[3];
}
if(timer >= 4f)
{
scoreCount.sprite = numbersprite[4];
}
if(timer >= 5f)
{
scoreCount.sprite = numbersprite[5];
}
if(timer >= 6f)
{
scoreCount.sprite = numbersprite[6];
}
}
}
}
What I want is to make it so that it both displays a specific sprite after a certain amount of time has based but also not have to resort to using any of this. Is there any way I can make this work?
If you want some more information I can give you that.

This code is the ultimate solution to the problem and can support sprite indefinitely. It is also fully optimized. Just put the sprites 0 to 9 in the first list and the images in the second list respectively.
public Sprite[] spriteNumbers = new Sprite[10]; // fill with numbers
public List<Image> spriteFieds; // set Images Based on a unit of tens of hundreds
public void Start() => InvokeRepeating(nameof(SyncTimer), 0f, 1f);
public void SyncTimer()
{
for (var i = 0; i < spriteFieds.Count; i++)
{
var index = (int) (Time.time / Mathf.Pow(10, i) % 10);
spriteFieds[i].sprite = spriteNumbers[index];
}
}
How to make Stop Timer?
Here I created a standalone timer field and you can stop the step timer by pressing the Space key. You can also reset the timer with the R key, for example.
public Sprite[] spriteNumbers = new Sprite[10]; // fill with numbers
public List<Image> spriteFieds; // set timer Fields
public bool isStop;
public float timer;
public void Update()
{
if (Input.GetKeyDown(KeyCode.Space)) isStop = !isStop;
if (Input.GetKeyDown(KeyCode.R))
{
timer = 0;
SyncTimer();
}
if (!isStop)
{
timer += Time.deltaTime;
SyncTimer();
}
}
public void SyncTimer()
{
for (var i = 0; i < spriteFieds.Count; i++)
{
var index = (int) (timer / Mathf.Pow(10, i) % 10);
spriteFieds[i].sprite = spriteNumbers[index];
}
}
The Timer Result:

I'm assuming you have 10 sprites for you numbers 0-9.
numberSprite[0] would hold the sprite for "0", numberSprite[1] would hole "1", etc.
Let's say the timer is at 319.8f seconds on the back end. You would want 3 sprites to display: 3, 1, 9.
To do this, you'll need to break your timer value and sprite into the hundredths, tenths, and seconds individually. You could do this:
int timerInt = (int)Mathf.floor(timer); //Get the int value of the timer
int hundredth = (timerInt/100) % 10; // (319/100) => 3 ... (3 % 10) => 3
scoreCountHundredths.sprite = numberSprite[hundredth];
int tenth = (timerInt /10) % 10; //(319/10) => 31 ... (31 % 10) => 1
scoreCountTenths.sprite = numberSprite[tenth];
int second = timerInt % 10; // (319 % 10) => 9
scoreCountSeconds.sprite = numberSprite[second];
With the above code, your timer should correctly update to any number between 000-999 requiring only 10 sprites uploaded. Additionally, it will automatically loop if your timer goes above 999 due to the modulo (%) logic.
Warning. Coroutines or InvokeRepeating may be a trap here:
Coroutines can be used to track the time between updating the sprites, but you'll likely be wanting to tie this display directly to the in-game time. relying on coroutines to update the sprite de-couples the in-game timer from the display, as they do not have built-in catchup behaviour. If your frames are slightly delayed or lag at all, you run the risk of the time running slower when using coroutines or InvokeRepeating.

Coroutines are perfect for this. Try this code here:
public Image image;
public Sprite[] sprites;
private bool isStop = true;
private void Start()
{
isStop = false;
StartCoroutine(Timer());
}
private IEnumerator Timer()
{
while (!isStop)
{
for (int i = 0; i < sprites.Length; i++)
{
if (isStop) break;
image.sprite = sprites[i];
yield return new WaitForSeconds(1f);
}
}
}

You can convert float to int
int spriteIndex = (int)Math.Round(timer);
And used spriteIndex as index to array sprites.
Or... if you need used different time interval for every sprite, you can make special struct for this animation.
For example:
[Serializable]
public struct SpriteFrame
{
public Sprite FrameSprite;
public float TimeToShow;
}
public class SpriteAnimationComponent : MonoBehaviour
{
public Image ScoreCount;
public List<SpriteFrame> Frames = new List<SpriteFrame>();
private int _currentFrame = 0;
private float _currentPlayAnimationTime = 0f;
private bool IsPlay => _currentFrame < Frames.Count;
public void Start()
{
UpdateFrame();
}
public void Update()
{
if(!IsPlay)
return;
_currentPlayAnimationTime += Time.deltaTime;
if(NeedShowNextFrame())
ShowNextFrame();
}
private bool NeedShowNextFrame()
=> Frames[_currentFrame].TimeToShow < _currentPlayAnimationTime;
private void ShowNextFrame()
{
_currentPlayAnimationTime -= Frames[_currentFrame].TimeToShow;
_currentFrame++;
if(IsPlay)
{
UpdateFrame();
}
}
private void UpdateFrame()
{
ScoreCount.sprite = Frames[_currentFrame].FrameSprite;
}
}
You need used SerializableAttribute ([Serializable]) on SpriteFrame for show struct in Unity Inspector. In current code animation show once, but you can make it loop. For loop animation just add _currentFrame %= Frames.Count after _currentFrame++

Related

How can I display pixel by pixel the calculation of a circle?

Few days before, I asked you to help me because I couldn't show the drawing of a circle pixel by pixel (the drawing got stuck). #James_D found the solution and told me it was because a background-thread was trying to modify the UI. Indeed, I have a GraphicEngine class that extends Service : this class is this background-thread and its aim was to calculate and modify the image (now its aim is just to calculate the circle's pixels).
Well, finally, thanks to #James_D, I have now some classes :
GraphicEngine,
ImageAnimation which contains the animation of drawing,
DialogCreationOfCircularGradation, which is a dialog containing a button "OK, draw circle !" and which handles this event.
The below source contains the answer of my program to the user-event "Draw a circle". It gets several Textfield's input and give it to the GraphicsEngine. Moreover it asks the latter to do the calculation of the circle's pixels and asks the ImageAnimation to display the calculation pixel by pixel, during the calculation done by the GraphicsEngine.
CLASS DialogCreationOfCircularGradation
public void dialogHandleEvents() {
Optional r = this.showAndWait();
if(r.isPresent() && r.get() == ButtonType.OK) { // The user clicks on "OK, draw the circle !"
int radius = Integer.parseInt(this.dialog_field_radius.getText());
[...]
this.gui.getImageLoader().loadImageFromUsersPreferences(x0 + thickness + 2*radius, y0 + thickness + 2*radius);
this.gui.getGraphicEngine().setOperationToDo("Circle");
this.gui.getGraphicEngine().setRadius(radius);
[...]
this.gui.getGraphicEngine().restart();
this.gui.getImageAnimation().start();
}
}
The below code is GraphicsEngine's one. As you can see, there is in particular one important variable which is incremented during the circle algorithm : its name is counter_max. Why this variable is important ? Because it's necessary used in the class ImageAnimation. Look at its source after GraphicsEngine's one.
CLASS GraphicEngine
public class GraphicEngine extends Service<Void> {
private int image_width, image_height;
private PixelReader pixel_reader;
private BlockingQueue<Pixel> updates;
private String operation_to_do;
private int radius; [...]
public void setOperationToDo(String operation_to_do) {
this.operation_to_do = operation_to_do;
}
public Task<Void> createTask() {
return new Task<Void>() {
protected Void call() {
switch(operation_to_do) {
[...]
case "Circle" :
traceCircularGradation();
break;
}
return null;
}
};
}
private void traceCircularGradation() {
double w = 2 * 3.141, precision = 0.001;
long counter_max = 0;
int x, y;
this.gui.getImageAnimation().setMax(counter_max);
double[] rgb_gradation;
for (double current_thickness = 0; current_thickness <= this.thickness; current_thickness++) {
for (double angle = 0; angle <= w; angle += precision) {
x = (int) ((current_thickness + radius) * Math.cos(angle) + x0);
y = (int) ((current_thickness + radius) * Math.sin(angle) + y0);
if(x >= 0 && y >= 0) {
counter_max++;
rgb_gradation = PhotoRetouchingFormulas.chromatic_gradation(angle, w);
updates.add(new Pixel(x, y, Color.color(rgb_gradation[0], rgb_gradation[1], rgb_gradation[2])));
}
}
}
this.gui.getImageAnimation().setMax(counter_max);
}
The variable counter_max is used in ImageAnimation (its name has changed, its called : max). It's useful in the last if : if (count >= max).
max/counter_max represent the number of modified pixels. I can't replace it with image_width * image_height because in the circle algorithm, only the circle's pixels are drawn/modified. The other pixels of the image are not.
So I have either to compute counter_max as I did here in the for loops and then give it to ImageAnimation, or find a mathematical formula to determine it before the for. In the first case, the display of the circle doesn't work.
It would be really perfect if a formula existed.
CLASS ImageAnimation
public class ImageAnimation extends AnimationTimer {
private Gui gui;
private long max;
private long count, start;
ImageAnimation (Gui gui) {
this.gui = gui;
this.count = 0;
this.start = -1;
}
public void setMax(long max) {
this.max = max;
}
#Override
public void handle(long timestamp) {
if (start < 0) {
start = timestamp ;
return ;
}
WritableImage writable_image = this.gui.getWritableImage();
BlockingQueue<Pixel> updates = this.gui.getUpdates();
while (timestamp - start > (count* 5_000_000) / (writable_image.getWidth()) && ! updates.isEmpty()) {
Pixel update = updates.remove();
count++;
writable_image.getPixelWriter().setColor(update.getX(), update.getY(), update.getColor());
}
if (count >= max) {
this.count = 0;
this.start = -1;
stop();
}
}
public void startAnimation() {
this.start();
}
}
If you need to compute max later, then you can't initialize it to zero (because if you do any updates before it is set to its "correct" value, then count will exceed max and you will stop the animation). So just initialize it to something it will never reach, e.g. Long.MAX_VALUE.
Relatedly, since you are accessing max from multiple threads, you should either synchronize access to it, or (better) use an AtomicLong. I.e.
public class ImageAnimation extends AnimationTimer {
private Gui gui;
private AtomicLong max;
private long count, start;
ImageAnimation (Gui gui) {
this.gui = gui;
this.count = 0;
this.start = -1;
this.max = new AtomicLong(Long.MAX_VALUE);
}
public void setMax(long max) {
this.max.set(max);
}
#Override
public void handle(long timestamp) {
if (start < 0) {
start = timestamp ;
return ;
}
WritableImage writable_image = this.gui.getWritableImage();
BlockingQueue<Pixel> updates = this.gui.getUpdates();
while (timestamp - start > (count* 5_000_000) / (writable_image.getWidth()) && ! updates.isEmpty()) {
Pixel update = updates.remove();
count++;
writable_image.getPixelWriter().setColor(update.getX(), update.getY(), update.getColor());
}
if (count >= max.get()) {
this.count = 0;
this.start = -1;
this.max.set(Long.MAX_VALUE);
stop();
}
}
public void startAnimation() {
this.start();
}
}

Why do I get an IndexOutOfBoundsException?

Here is my for loop, is my for loop wrong? I have not been doing loops for a very long time so I am still a beginner and I would really appreciate if someone could take a look at my problem.
for (int i = 0; i < tabLayout.getTabCount(); i++) {
TabLayout.Tab tab = tabLayout.getTabAt(i);
if (tab != null) {
tab.setCustomView(mSectionsPagerAdapter.getTabView(i));
}else{
System.out.println("ERROR---TAB IS NULL---ERROR");
}
}
and here is my Count Method and Char
#Override
public int getCount() {
// Show 3 total pages.
return 4;
}
#Override
public CharSequence getPageTitle(int position) {
// Generate title based on item position
// return tabTitles[position];
// getDrawable(int i) is deprecated, use getDrawable(int i, Theme theme) for min SDK >=21
// or ContextCompat.getDrawable(Context context, int id) if you want support for older versions.
// Drawable image = context.getResources().getDrawable(iconIds[position], context.getTheme());
// Drawable image = context.getResources().getDrawable(imageResId[position]);
Drawable image = ContextCompat.getDrawable(MainActivity.this, imageResId[position]);
image.setBounds(0, 0, image.getIntrinsicWidth(), image.getIntrinsicHeight());
SpannableString sb = new SpannableString(" ");
ImageSpan imageSpan = new ImageSpan(image, ImageSpan.ALIGN_BOTTOM);
sb.setSpan(imageSpan, 0, 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
return sb;
}

How to scroll a Pager programatically AND smoothly

I'm using this code to scroll programatically my pager
public void MoveNext(View view) {
pager.setCurrentItem(pager.getCurrentItem() + 1);
}
public void MovePrevious(View view) {
pager.setCurrentItem(pager.getCurrentItem() - 1);
}
The code work perfect but the transition is too fast. How can I introduce a delay so that the scrolling would be done more smoothly?
I believe there are two ways can achieve this goal, I've tried by ViewPager's fakeDrag() but which doesn't work perfect, luckly, another way does, by simulate touch motion event and use ObjectAnimator to specify the animation duration then make control the scroll speed become true.
public class ViewPagerActivity extends FragmentActivity
implements View.OnClickListener, Animator.AnimatorListener {
private ViewPager mViewPager;
private View btnTriggerNext;
private View btnTriggerPrev;
#Override
protected void onCreate(...) {
super...;
setContentView(R.layout.layout_xml);
mViewPager = findViewById(...);
btnTriggerNext = findViewById(R.id.btnTriggerNext);
btnTriggerNext.setOnClickListener(this);
btnTriggerPrev = findViewById(R.id.btnTriggerPrev);
btnTriggerPrev.setOnClickListener(this);
}
private boolean mIsInAnimation;
private long mMotionBeginTime;
private float mLastMotionX;
#Override
public void onClick(View v) {
if (mIsInAnimation) return;
ObjectAnimator anim;
if (v == btnTriggerPrev) {
if (!hasPrevPage()) return;
anim = ObjectAnimator.ofFloat(this, "motionX", 0, mViewPager.getWidth());
}
else if (v == btnTriggerNext) {
if (!hasNextPage()) return;
anim = ObjectAnimator.ofFloat(this, "motionX", 0, -mViewPager.getWidth());
}
else return;
anim.setInterpolator(new LinearInterpolator());
anim.addListener(this);
anim.setDuration(300);
anim.start();
}
public void setMotionX(float motionX) {
if (!mIsInAnimation) return;
mLastMotionX = motionX;
final long time = SystemClock.uptimeMillis();
simulate(MotionEvent.ACTION_MOVE, mMotionBeginTime, time);
}
#Override
public void onAnimationEnd(Animator animation) {
mIsInAnimation = false;
final long time = SystemClock.uptimeMillis();
simulate(MotionEvent.ACTION_UP, mMotionBeginTime, time);
}
#Override
public void onAnimationStart(Animator animation) {
mLastMotionX = 0;
mIsInAnimation = true;
final long time = SystemClock.uptimeMillis();
simulate(MotionEvent.ACTION_DOWN, time, time);
mMotionBeginTime = time;
}
// method from http://stackoverflow.com/a/11599282/1294681
private void simulate(int action, long startTime, long endTime) {
// specify the property for the two touch points
MotionEvent.PointerProperties[] properties = new MotionEvent.PointerProperties[1];
MotionEvent.PointerProperties pp = new MotionEvent.PointerProperties();
pp.id = 0;
pp.toolType = MotionEvent.TOOL_TYPE_FINGER;
properties[0] = pp;
// specify the coordinations of the two touch points
// NOTE: you MUST set the pressure and size value, or it doesn't work
MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[1];
MotionEvent.PointerCoords pc = new MotionEvent.PointerCoords();
pc.x = mLastMotionX;
pc.pressure = 1;
pc.size = 1;
pointerCoords[0] = pc;
final MotionEvent ev = MotionEvent.obtain(
startTime, endTime, action, 1, properties,
pointerCoords, 0, 0, 1, 1, 0, 0, 0, 0);
mViewPager.dispatchTouchEvent(ev);
}
private boolean hasPrevPage() {
return mViewPager.getCurrentItem() > 0;
}
private boolean hasNextPage() {
return mViewPager.getCurrentItem() + 1 < mViewPager.getAdapter().getCount();
}
#Override
public void onAnimationCancel(Animator animation) {
}
#Override
public void onAnimationRepeat(Animator animation) {
}
}
cause it was simulating touch event, so please use a proper duration(less than 600ms will be nice) to do scrolling, when scroll in progress, put down finger would stop it and cause some bugs.
Change
pager.setCurrentItem(pager.getCurrentItem() + 1);
to
pager.setCurrentItem(pager.getCurrentItem() + 1,true);
This will call the method setCurrentItem(int item,boolean smoothScroll) which will scroll smoothly to the mentioned item instead of transitioning immediately

Stop Skeleton Flickering in Kinect for Windows

Currently working on a project to find the X,Y position of the using who is standing in front of the kinect.
Even though the user is standing still the X,Y keep changing because the kinect skeleton is flickering. Could someone help me how to figure out a way to stop the skeleton flickering
Have you set the smoothing parameter? The following settings result in very smooth movements, but with a lot of latency.
TransformSmoothParameters smoothingParam = new TransformSmoothParameters
{
Smoothing = 0.7f;
Correction = 0.3f;
Prediction = 1.0f;
JitterRadius = 1.0f;
MaxDeviationRadius = 1.0f;
};
sensor.SkeletonStream.Enable(smoothingParam);
If that is not working good enough for you, I had developed a helper class for one of my kinect projects to smooth the coordinates of joints. You have to create an instance of the following helper class for each smoothed joint and call GetSmoothedXPosition/GetSmoothedYPosition with the screen coordinates of the joint:
internal class JointSmoothing
{
#region Constants and Enumerations
private const int QueueLength = 7;
private const double BaseValue = 0.9;
#endregion
#region Fields
private readonly Queue<double> myXValues = new Queue<double>(QueueLength);
private readonly Queue<double> myYValues = new Queue<double>(QueueLength);
#endregion
#region Public Methods
public double GetSmoothedXPosition(double x)
{
if (myXValues.Count >= QueueLength)
{
myXValues.Dequeue();
}
myXValues.Enqueue(x);
return ExponentialMovingAverage(myXValues);
}
public double GetSmoothedYPosition(double y)
{
if (myYValues.Count >= QueueLength)
{
myYValues.Dequeue();
}
myYValues.Enqueue(y);
return ExponentialMovingAverage(myYValues);
}
public void Clear()
{
myXValues.Clear();
myYValues.Clear();
}
#endregion
#region Private Implementation
private static double ExponentialMovingAverage(Queue<double> values)
{
double numerator = 0;
double denominator = 0;
int valueCount = values.Count;
int weight = valueCount - 1;
foreach (double value in values)
{
numerator += value * Math.Pow(BaseValue, weight);
denominator += Math.Pow(BaseValue, weight);
weight--;
}
double average = values.Sum();
average /= values.Count;
numerator += average * Math.Pow(BaseValue, valueCount);
denominator += Math.Pow(BaseValue, valueCount);
return numerator / denominator;
}
#endregion
}

Algorithm to fire sequential keystrokes

I want to write an algorithm to sequentially press keys F1-F3. My form has these controls:
lblF1
textboxF1
lblF2
textboxF2
lblF3
textboxF3
btnStart
In textboxF1-textboxF3 the time in seconds is entered. This when the program is to press the hotkey. It is important that the program can't press two keys at once, for example F1 and F2. It may not press more than one key in a second. When I click on btnStart it calls Run().
This is how I tried to resolve this:
static int counterF1 = 9999;
static int counterF2 = 9999;
static int counterF3 = 9999;
public void Run()
{
counterF1 = 9999;
counterF2 = 9999;
counterF3 = 9999;
while (true)
{
Loop();
}
}
public void Loop()
{
bool used = false;
if (counterF1 >= (int)textboxF1.text)
{
counterF1 = PressKey(VK_F1);
used = true;
}
if (counterF2 >= (int)textboxF2.text)
{
counterF2 = PressKey(VK_F2);
used = true;
}
if (counterF3 >= (int)textboxF3.text)
{
counterF3 = PressKey(VK_F3);
used = true;
}
if (used == false)
{
IncrementCounters();
Delay(1000);
}
}
public double PressKey(uint key)
{
myPostMessageA(hWindow, WM_KEYDOWN, (uint)key, (uint)key);
IncrementCounters();
return 1; //return 1 because one second
}
public void IncrementCounters()
{
counterF1++;
counterF2++;
counterF3++;
}
But often it doesn't press any key (it is possible it is too late, but can't be an omission). Can you explain how to make an algorithm for this?
We will use a class KeyStroke that stores the necessary data for a special key:
public class KeyStroke
{
public int period { get; set; } // Period in which to hit key
public int next { get; set; } // ticks to the next hit of this key
public int VK { get; set; } //KeyCode
}
public List<KeyStroke> keys = new List<KeyStroke>();
An Initialize() method is needed to read the data from the text boxes and to init the simulation. We utilize a timer with the interval of one second to run the simulation. In my example, I don't read from textboxes, but use constant values. Add the input and error handling. If you use WPF, you can bind the KeyStroke objects to the textboxes.
void Init()
{
//Initialize keys with according periods from input
keys.Clear();
keys.Add(new KeyStroke() { VK = VK_F1, period = 1, next = 1 });
keys.Add(new KeyStroke() { VK = VK_F2, period = 10, next = 10 });
keys.Add(new KeyStroke() { VK = VK_F3, period = 5, next = 5 });
//sort keys by period (descending), in order to handle long period keys, too
keys.Sort((first, second) => second.period.CompareTo(first.period));
//Start the program
var t = new DispatcherTimer();
t.Interval = TimeSpan.FromSeconds(1);
t.Tick += new EventHandler(t_Tick);
t.Start();
}
The tick event is similar to yours:
void t_Tick(object sender, EventArgs e)
{
bool used = false;
foreach (var key in keys)
{
if (key.next <= 0 && !used)
{
PressKey(key.VK);
key.next = key.period;
used = true;
}
key.next--;
}
}

Resources