C# (WPF) to realize the King of Fighters (1)

       This game is relatively large, so I plan to introduce it in several articles. This section first introduces the implementation of the basic P1. In fact, those who know something about games know that fighting games are the most difficult game types to write. Whether it is logic, complex skill control, or collision detection, they are more complicated, so I try to be perfect.

<Canvas Name="MyCanvas" ></Canvas>

First of all, the layout we selected in the main interface is the Canvas layout. Why, because this layout is very convenient when we have image resources in our home, and can accurately control the position, which is convenient for us to make attribute animations. (If you don’t understand here, you can first learn the layout of WPF on Baidu, among which Grid, Canvas, StackPanel, DockPanel, etc. are more important, and I will update related articles later). Then define an Image object as P1:


Let's take a look at how to load the protagonist. We know that in general fighting games, the characters are not standing still, but will shake, which means that we must load them into animation.

Let's learn how to implement animation in WPF:

       1. The way of the time container (TimeLine may be a more accurate translation of the timeline, but I prefer the translation of the time container)

Because this method specifies a time period, which is the capacity of the time container, and then adds the action to the time container, and finally loads the time container into it to achieve the animation effect. This is generally to achieve attribute animation. What is property animation? First of all, we know that each object has various properties, such as the size of a button (this is called property), the position of the button (this is called property), the color of the button and so on. Therefore, property animation is an animation that dynamically realizes the change of object properties within a specified time.

public partial class MainWindow : Window
    {
        Rectangle rect;
        public MainWindow()
        {
            InitializeComponent();
            rect = new Rectangle();
            rect.Stroke = Brushes.Black;
            rect.Fill = Brushes.Red;
            //These two sentences must have, although they are not loaded into (0, 0) by default, but there are no two sentences to set this property, below
            rect.SetValue(Canvas.LeftProperty,0D);
            rect.SetValue(Canvas.TopProperty,0D);
            rect.Height = 100;
            rect.Width = 100;
            MyCanvas.Children.Add(rect);
        }

        private void MyCanvas_MouseDown(object sender, MouseButtonEventArgs e)
        {
            Point MoveTO = new Point();
            MoveTO = e.GetPosition(MyCanvas);

            //Storyboard, which can load multiple time containers at the same time
            Storyboard MyStory = new Storyboard();
            //move the X axis
            DoubleAnimation MyXAnimation = new DoubleAnimation(MoveTO.X, TimeSpan.FromMilliseconds(2000));
            Storyboard.SetTargetProperty(MyXAnimation, new PropertyPath(Canvas.LeftProperty));
            MyStory.Children.Add(MyXAnimation);
            MyStory.Begin (rect);
            //Move the Y axis
            DoubleAnimation MyYAnimation = new DoubleAnimation(MoveTO.Y, TimeSpan.FromMilliseconds(2000));
            Storyboard.SetTargetProperty (MyYAnimation, new PropertyPath (Canvas.TopProperty));
            MyStory.Children.Add(MyYAnimation);
            MyStory.Begin (rect);
        }

This code is to animate the mouse click on the square to move to the clicked position, but if you just write it like this, you will find a problem, only when you click on the inside of the square, it will move and it will have no effect outside the click, why? ? Because when we join Canvas, if we do not operate on it, we only think that only the area occupied by the current control is valid. Solutions are:

1. Add an invalid background color to Xaml to achieve the effect.

<Canvas Name="MyCanvas" Background="White" MouseDown="MyCanvas_MouseDown"/>

2. Then do not use the mouse click function in Canvas, but use the mouse click function of the form. The implementation effect of these two methods is the same. Of course, attribute animation is not only about DoubleAnimation, but also other attributes, such as PointAnimation, etc. (but generally can be combined with DoubleAnimation). If you want to know more about it, you can go to Microsoft's official website to see related 's documentation (of course I will update it later).

       If we have learned the above animation effects, then we can complete the movement of the protagonist, yes, it is very simple! The movement of the protagonist is just the movement of the position of the picture, which is an effect with the rectangle above. However, it seems that the movement of the character alone is not enough. We also need to constantly realize the walking effect of the picture and the skill effect when walking. So let's introduce the second method of animation generation.

       2. The animation generated by the timer.

There are Timer and DispatcherTimer timers in WPF, but it is recommended to use the DispatcherTimer timer. Anyone who has learned visual programming and used a timer should know what it is. As the name implies, it is to continuously trigger an action within a specified time. In WPF, it is to call a function at a specified time interval to complete a specified action.

       public MainWindow()
        {
            InitializeComponent();
            Spirit = new Image();
            Canvas.SetLeft(Spirit,0D);
            Canvas.SetBottom(Spirit,150D);
            MyCanvas.Children.Add(Spirit);
            ResourceAdd();                                                                   
          
            // standing timer
            DispatcherTimer StandTimer = new DispatcherTimer();
            StandTimer.Interval = TimeSpan.FromMilliseconds (80);
            StandTimer.Tick += new EventHandler(StandTimer_Tick);
            StandTimer.IsEnabled = true;
            StandTimer.Start();
If the timer is to be turned off and off continuously, I recommend applying it in the loading function or this function, or applying it as a global variable. Because this can avoid our non-stop application in other functions and prevent uncontrollable errors, so we only need to set its IsEnable property or call the Stop() function to control the shutdown and start of the timer.
private void StandTimer_Tick(Object sender,EventArgs s)
        {
            if (!Is_Using_Skill)
            {
                switch (Go_Type)
                {
                    case 0: //stand
                        {
                            for (int i = 0; i < Stand_Total_Count; i++)
                            {
                                if (i == Stand_Real_Count)
                                {
                                    Spirit.Source = Stand[i];
                                    Stand_Real_Count++;
                                    if (Stand_Real_Count == Stand_Total_Count)
                                        Stand_Real_Count = 0;
                                    break;
                                }
                            }//end of for
                            break;
                        }// end of case 0
                    case 1: //forward
                        {
                            for (int i = 0; i < Go_Ahead_Total_Count; i++)
                            {
                                if (i == Go_Ahead_Real_Count)
                                {
                                    Spirit.Source = Go_Ahead_List[i];
                                    Go_Ahead_Real_Count++;
                                    if (Go_Ahead_Real_Count == Go_Ahead_Total_Count)
                                        Go_Ahead_Real_Count = 0;
                                    break;
                                }
                            }//end of for
                            break;
                        }//end of case 1
                    case 2: // back
                        {
                            for (int i = 0; i < Go_Back_Total_Count; i++)
                            {
                                if (i == Go_Back_Real_Count)
                                {
                                    Spirit.Source = Go_Back_List[i];
                                    Go_Back_Real_Count++;
                                    if (Go_Back_Real_Count == Go_Back_Total_Count)
                                        Go_Back_Real_Count = 0;
                                    break;
                                }
                            }//end of for
                            break;
                        }//End of case 2
                }//end of switch
            }
        }

Then we need to load different animations according to a type value, such as walking, standing, stepping back, etc. The prerequisite for this logic is, of course, that we need to load resources into the program, and we create a separate role resource loading function.

public void ResourceAdd()
        {
            //Standing resource loading
            Uri StandUri = new Uri("Image/CT_Stand.gif", UriKind.RelativeOrAbsolute);//(The second parameter specifies the Uri address)
            GifBitmapDecoder Stand_decoder2 = new GifBitmapDecoder(StandUri, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.Default);
            foreach (BitmapSource temp in Stand_decoder2.Frames)
            {
                Stand.Add(temp);
                Stand_Total_Count++; //Record the total number of frames loaded
            }

This is just a way to load resources, because my picture is a Gif, so a Gif decoder is called. If your image resource is jpg or png, it can be loaded directly, that is, you can not use this function, you can directly load the image resource into it when you use it, for example:

Spirit.Source = new BitmapImage(new Uri("xxx"+ImageNumber.Tostring()+".xxx"),UriKin.Relative);

where you can use ImageNumber to control which frame to load into memory. However, in most cases, the number of frames in general fighting games is relatively large, so most of the resources are directly packaged into a Gif. Of course, you can also use some picture software to convert a series of pictures such as png into Gif, of course If you are interested, you can make one yourself, and the workload is not as big as you think. So if you are a Gif, please remember the above function, it is very wonderful! The second sentence about the parameters of the decoder, you choose according to your needs, is to control some ways of loading, you need to make different choices according to your resources and your needs.

       Then we just want to realize the movement of the character by pressing the button, the crease is simple, we can add a corresponding event of the button.

            if (Key_Down_Times <= 2 && !Is_Using_Skill)
            {
                StandTimer.IsEnabled = true;
                Point MoveTo = new Point(Canvas.GetLeft(Spirit), Canvas.GetTop(Spirit));
                Storyboard Go_Story = new Storyboard();
                if (e.Key == Key.Left && Canvas.GetLeft(Spirit) > 0)
                {
                    Go_Type = Go_Back;
                    MoveTo.X -= Go_Speed;
                    DoubleAnimation BackAnimation = new DoubleAnimation(MoveTo.X, TimeSpan.FromMilliseconds(500));
                    Storyboard.SetTargetProperty(BackAnimation, new PropertyPath(Canvas.LeftProperty));
                    Go_Story.Children.Add(BackAnimation);
                    Go_Story.Begin(Spirit);
                    Key_Down_Times++;
                }
                else if (e.Key == Key.Right && Canvas.GetLeft(Spirit) < ActualWidth)
                {
                    Go_Type = Go_Ahead;
                    MoveTo.X += Go_Speed;
                    DoubleAnimation AheadAnimation = new DoubleAnimation(MoveTo.X, TimeSpan.FromMilliseconds(500));
                    Storyboard.SetTargetProperty(AheadAnimation, new PropertyPath(Canvas.LeftProperty));
                    Go_Story.Children.Add(AheadAnimation);
                    Go_Story.Begin(Spirit);
                    Key_Down_Times++;
                }
            }// end of if
There is no good explanation, it is the use of the above attribute animation, and then the timer is running all the time, which produces the effect of movement. The two judgment conditions of if are some boundary judgments, such as not being able to move across the screen (of course, the scrolling effect of the map has not been implemented yet, and it will be added later!)

       WPF also has an implementation method to achieve animation effects, but since this game may not need it, I will not introduce it first (I will make a game similar to a legend later, which I will introduce), the implementation of these animations has its own advantages. The above probably can realize the basic functions of a simple P1. Let's make some optimizations:

      Question 1: You will find that if you try to keep pressing the forward button, what will happen? Yes, the characters move faster and faster, why is that? The reason is that there is no limit to the response event of the button, and we can always respond, then it keeps moving, which will cause this error. So the solution is actually very simple, we can set a bool variable to control, we can set this bool variable to a value in the KeyDown response event, and then change its value in the KeyUp variable, then we can keep pressing It only responds once, and it only works when you lift it up and press it.

       But we found that this has another problem, that is, when we keep pressing a button in a fighting game, it will always move in one direction, not necessarily press and lift, because this way the game The experience will be much worse. So how do we keep pressing and the character does not suddenly accelerate but moves at a steady speed?

       Yes, maybe you should have already thought, if we can start executing the next action when the first animation effect is completed, then we only need to control it according to the time interval.

Solution 1. Set a LastSecond variable of DateTime type, remember that time point from the execution of each animation, and then get the current time through DateTime.Now every time you press a button, and calculate the time interval passed by the difference. , so that the desired effect can be achieved.

Solution 2. Set up a timer and use it separately for timing.

        Question 2. When the character moves or releases the skill, there will be a jitter effect, or the release of some skills that should not have moved the position, the character will have a displacement effect. In fact, the reason is because of the problem of your resources. Because of the different pixels, the height and width of the loaded images are different, and we started with Spirit.SetValue(Canvas.TopProperty,xD), which is the top of the control character, so that it will Produces a downward displacement of the character.

Solution 1. Use some software for image manipulation to modify the pixels of the image.

Solution 2. Set it to Spirit.SetValue(Canvas.BottomProperty, xD), because the fighting game does not move down.        

        Question 3. When the character releases the skill, the character is performing the walking effect at the same time, resulting in the problem of non-stop ghosts and animals. This is caused by the conflict between the two timers, because both timers are running and loading their own resource frames, so of course this problem will occur.

Method: Turn off the movement timer, set a separate timer when you release the skill, the setting method is the same as the movement, then turn off the movement timer when you enter the only release, and turn off the skill timer when the skill is released to the last frame to start the movement timer.

        Question 4. The faster the character skill is released or moved, for example, the first release of the same skill takes only 3 seconds, then the second time it takes 1.5 seconds, and then the third time it takes 1 second.

Solution: When this problem occurs, it is most likely that you have new the timer many times and then start it, so the solution is to new the timer during initialization, and then use the isEnable property to complete when it needs to be started or stopped. .

       Question 5. An error is reported when loading the resource, saying that the resource cannot be found.

Solution: I found that when using Vs2013, you copy the picture directly into the program, but if the program cannot find the resource through the relative path, then you need to manually copy the picture to the bin/debug directory.

Note: The release effect of the combination key of the skill will be realized later.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324517690&siteId=291194637