Стигнавме до најинтересниот дел (барем за мене 🙂 ) од овој серијал. По цртањето на објектите кое што беше опфатено во вториот дел, во овој дел ќе додадеме движење, инпут (со тастатура и допир) и колизии.

Движење

Прво, ќе имплементираме движење на топчето бидејќи топчето е независно од надворешен инпут.

Отворете ја GameObject.cs класата и додадете го методот Move:

public virtual void Move(Vector2 amount)
{
	Position += amount;
}

 

Движењето во 2Д игри е прилично едноставно. Се што се прави е, на вектор за позиција се додава друг вектор за да се добие нова позиција. Со повторување на овој метод 30 пати во секунда, ќе се добие некакво движење.

На првата линија забележувате искористен е зборот virtual. Овој метод ќе го искористиме во наследените класи Player и Ball со тоа што на двете класи ќе им додадеме уште неколку линии кои што не се заеднички меѓу нив.

Отворете ја Ball.cs класата и преправете ја во следното:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Xna.Framework;

namespace PongClone
{
	public class Ball : GameObject
	{
		public Vector2 Velocity;
		public Random random;

		public Ball()
		{
			random = new Random();
		}
	}
}

 

Топчето ќе има променлива брзина, затоа ни треба еден вектор кој што ќе ја прати брзината (и насоката) на топчето. Random објектот ќе се користи за лансирање на топчето на левата или десната страна (50% веројатност на едната страна, 50% веројатност на другата страна). Конструкторот на линиите 15, 16, 17 и 18 се користи за креирање на Random објектот кој што ќе го користиме во Launch методот.

Додадете го следниот Launch метод во Ball.cs класата:

public void Launch(float speed)
{
	Position = new Vector2(Game1.ScreenWidth / 2 - Texture.Width / 2, Game1.ScreenHeight / 2 - Texture.Height / 2);
	// get a random + or - 60 degrees angle to the right
	float rotation = (float) ( Math.PI/2 + (random.NextDouble() * (Math.PI/1.5f) - Math.PI/3));

	Velocity.X = (float) Math.Sin(rotation);
	Velocity.Y = (float)Math.Cos(rotation);

	// 50% chance whether it launches left or right
	if (random.Next(2) == 1)
	{
		Velocity.X *= -1; //launch to the left
	}

	Velocity *= speed;
}

 

Не плашете се од математиката во овој метод, многу е едноставна, ќе ви објаснам што се случува на секоја линија.

Како што може да видите на првата линија, Launch методот добива параметар за брзина (има разлика помеѓу speed и Velocity; speed е брзина скалар, velocity е брзина со насока, вектор). Оваа брзина (speed) ќе ни ја претставува почетната брзина на топчето.

Во третата линија, се сетира почетната позиција на топчето (во нашиот случај, на центарот на екранот).

Потоа, во линија 5 креираме променлива за вредност на ротација кој што потоа во линиите 7 и 8 се користи за да се добие аголот на кој што ќе се лансира топчето. Доколку ротацијата е 0, Sin(0) = 0, и Cos(0) = 1. Бидејќи вредноста која што произлегува од Sin функцијата ја ставаме во Velocity.X, а вредностa што произлегува од Cos функцијата ја ставаме во Velocity.Y , топчето ќе се лансира надолу. Како што кажав на претходниот пост, (0,0) на екранот се наоѓа на горниот лев агол, тоа значи дека позитивната страна на Y оди надолу.

Најпрво на вредноста за ротација му даваме вредност Math.PI / 2 со која што ќе добиеме лансирање на десната страна бидејки Pi / 2 = 90 степени, Sin(90 степени) = 1, Cos(90 степени) = 0. Потоа на таа вредност додаваме нова вредност со користење на Random објектот за да генерираме вредност помеѓу +60 и –60 степени (во нашиот случај рачунаме со радијани, поради едноставност, бројките во ова објаснување ги конвертирам во степени).

Како дојдовме до +60 и –60? random.NextDouble() генерира вредности помеѓу 0.0 и 1.0. Таа вредност ја множиме со Math.PI / 1.5f (120 степени). Сега добиваме вредности од 0 до 120 степени. Затоа, на крај имаме – Math.PI / 3 (60 степени) со кој што го добиваме посакуваниот интервал (-60,+60)

Сега топчето иако ќе се лансира по случаен агол измеѓу –60 и +60 степени, постојано ќе се лансира на десната страна. Во линија 11 генерираме цел број помеѓу 1 и 0. Шансите да се добие 1 или 0 се 50-50. Доколку се добие 1, вредноста на Velocity.X ја множиме со –1 со што ја променуваме насоката на лансирање од десно на лево.

Конечно вектор брзината (Velocity) ја множиме со скаларната брзина (speed) за да ја добиеме конечната почетна брзина на топчето.

Напомена: Доколку оваа математика не ви е јасна, откако ќе го напишеме кодот во Game1.cs, може да се вратите на овој метод и да ги промените вредностите кои што се во rotation. Почнете со давање на вредност 0 и зголемувајте/намалувајте од тука за да видите што се случува со аголот на лансирање. Ако повторно не ви е јасно, слободно оставете коментар подолу.

Отворете ја Game1.cs класата и најгоре каде што ја дефиниравме првата константа PADDLE_OFFSET = 70, додадете нова константа за почетна брзина на топчето:

const float BALL_START_SPEED = 8f;

Избришете ја следната линијата за сетирање на позицијата на топчето во LoadContent бидејќи истата ја имаме во Launch методот:

ball.Position = new Vector2(ScreenWidth / 2 - ball.Texture.Width / 2, ScreenHeight / 2 - ball.Texture.Height / 2);

 

На тоа место ставете ја следната линија:

ball.Launch(BALL_START_SPEED);

 

Во Update методот, под линиите за ширина и висина на екранот, додадете ја линијата

ball.Move(ball.Velocity);

ScreenWidth = GraphicsDevice.Viewport.Width;
ScreenHeight = GraphicsDevice.Viewport.Height;
ball.Move(ball.Velocity);
base.Update(gameTime);

Оваа линија ќе го активира Move методот каде што се собираат векторите Position и Velocity, и резултатот се зачувува во Position.

Притиснете F5 или Debug –> Start Debugging за да ја пуштите апликацијата. Ќе забележите дека топчето се движи и после кратко време излегува од видното поле. Пред да преминеме на пишување логика за колизии, ќе го имплементираме инпутот за играчите.

Инпут

Најпрво ќе креираме нова класа која што ќе се користи за процесирање на инпутот од играчите. Притиснете десен клик на фолдерот ‘Classes’ Add –> Class… крстете ја новата класа ‘Input’ (без наводници).

Преправете ја Input.cs класата во следното:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Input.Touch;

namespace PongClone
{
    public static class Input
    {
        public static List<GestureSample> Gestures;

        static Input()
        {
            Gestures = new List<GestureSample>();
        }

    }
}

 

Ќе забележите дека се додадени неколку библиотеки кои што ќе се потребни за имплементирање на инпутот. Потоа ќе забележите дека креираме статична класа (линија 12). Тоа значи дека самата класа ни претставува еден и единствен објект (не ни требаат повеќе објекти за да се процесира инпут). Потоа во линија 14 декларираме една листа Gestures која што ќе ги чува сите инпути на допир (Touch). Во конструкторот static Input(), ја иницијализираме листата за да можеме да ја користиме покасно.

Прво ќе го напишеме методот за инпут од тастатура. Додадете го следниот метод во класата:

public static Vector2 GetKeyboardInputDirection(PlayerIndex playerIndex)
{
	Vector2 direction = Vector2.Zero;
	KeyboardState keyboardState = Keyboard.GetState(playerIndex);

	if (playerIndex == PlayerIndex.One)
	{
		if (keyboardState.IsKeyDown(Keys.W))
			direction.Y += -1;
		if (keyboardState.IsKeyDown(Keys.S))
			direction.Y += 1;
	}

	if (playerIndex == PlayerIndex.Two)
	{
		if (keyboardState.IsKeyDown(Keys.Up))
			direction.Y += -1;
		if (keyboardState.IsKeyDown(Keys.Down))
			direction.Y += 1;
	}
	return direction;
}

 

Oвој метод враќа Vector2 објект и прима еден параметар PlayerIndex со кој што се дефинира за кој играч станува збор (првиот, вториот итн.). Во третата линија иницијализираме еден вектор на (0,0) кој што ќе ја содржи насоката на движење. Во линија 4, декларираме KeyboardState променлива и ја земаме состојбата на тастатурата. Потоа во шестата линија проверуваме дали станува збор за првиот играч. Доколку одговорот е да, проверуваме дали буквата W е притисната. Ако е притисната, ја намалуваме вредноста на Y оската за 1 (Y се намалува ако се движиме нагоре). Истото се проверува и со буквата S за движење надолу. Доколку не станува збор за првиот играч, се проверуваат истите работи со различни копчиња (стрелките за горе и долу) за вториот играч. На крајот се враќа вредноста на векторот direction кој што ја содржи насоката. Насоката може да биде нагоре, надолу, или нема промена на местото ако нема никаков инпут.

Отворете ја Game1.cs класата и додадете уште една константа за брзината на играчите кога инпутот е преку тастатура:

const float KEYBOARD_PADDLE_SPEED = 10f;

 

Во Update методот, под линијата ball.Move(ball.Velocity); додадете ги следните линии:

ball.Move(ball.Velocity);

Vector2 player1Velocity = Input.GetKeyboardInputDirection(PlayerIndex.One) * KEYBOARD_PADDLE_SPEED;
Vector2 player2Velocity = Input.GetKeyboardInputDirection(PlayerIndex.Two) * KEYBOARD_PADDLE_SPEED;

player1.Move(player1Velocity);
player2.Move(player2Velocity);

 

Прво декларираме и ги иницијализираме брзините на играчите во два Vector2 променливи. Бидејќи методот што претходно го напишавме во инпут класата враќа вектор со насока, го множиме резултатот со константата каде што е дефинирана брзината. Потоа ги повикуваме Move методите за движење на играчите.

Притиснете F5 или Debug –> Start Debugging и тестирајте го движењето на играчите. Ќе забележите дека играчите го напуштаат видното поле, во следниот дел за колизии ќе ги поставиме границите на движење.

Пред да преминеме на колизии, најпрво ќе го имплементираме инпутот преку допир (Touch). Отворете ја Input.cs класата и додадете го следниот метод:

public static void ProcessTouchInput(out Vector2 player1Velocity, out Vector2 player2Velocity)
{
	Gestures.Clear();
	while (TouchPanel.IsGestureAvailable)
	{
		Gestures.Add(TouchPanel.ReadGesture());
	}
	player1Velocity = Vector2.Zero;
	player2Velocity = Vector2.Zero;

	foreach (GestureSample gestureSample in Gestures)
	{
		if (gestureSample.GestureType == GestureType.FreeDrag)
		{
			if (gestureSample.Position.X >= 0 && gestureSample.Position.X <= Game1.ScreenWidth / 2)
                        player1Velocity.Y += gestureSample.Delta.Y;
			if (gestureSample.Position.X >= Game1.ScreenWidth/2 && gestureSample.Position.X <= Game1.ScreenWidth)
                        player2Velocity.Y += gestureSample.Delta.Y;
		}
	}
}

 

Овој метод враќа два вредности како што може да се види во првата линија. Во линија 3 ја бришиме цела листа и потоа во while циклус проверуваме дали има некаков инпут преку допир. Доколку има, сите инпути ги додаваме во листата Gestures. Потоа ги иницијализираме векторите на (0,0) и ги проверуваме сите инпути што ги имаме во листата Gestures. Доколку имаме инпут со влечење (FreeDrag), проверуваме на која позиција од екранот се случило влечењето. Доколку се случило во левата половина, брзината од Y оската на влечењето ја додаваме на брзината на играчот во Y оската. Доколку влечењето е во десната половина, истата математика ја правиме за брзината на вториот играч.

Отворете ја Game1.cs класата и најгоре, додадете ја следната библиотека:

using Microsoft.Xna.Framework.Input.Touch;

 

Потоа, сменете го конструкторот во следното:

public Game1()
{
	_graphics = new GraphicsDeviceManager(this);
	Content.RootDirectory = "Content";
	TouchPanel.EnabledGestures = GestureType.FreeDrag;
}

Забележувате ја додадовме 5тата линија. Оваа линија ни дозволува инпут преку влечење на екранот.

Во Update методот, под повикувањето на методите за движење на играчите со тастатура, додадете ги следните линии:

player1.Move(player1Velocity);
player2.Move(player2Velocity);

Vector2 player1TouchVelocity, player2TouchVelocity;
Input.ProcessTouchInput(out player1TouchVelocity, out player2TouchVelocity);
player1.Move(player1TouchVelocity);
player2.Move(player2TouchVelocity);

 

Прво, во линија 4 декларираме два вектори каде што ќе ги чуваме вредностите за брзината на влечење. Откако ќе се повика методот за процесирање на Touch инпутот, повторно ги повикуваме методите за движење.

Како што е покажано на сликата подолу, сменете ја машината за дебагирање од Local Machine во Simulator за да може да го тестирате Touch инпутот.

Притиснете F5 или Debug –> Start Debugging за да го уклучите симулаторот и да го тестирате Touch инпутот.

Колизии

Имаме три работи кои што треба да ги средиме за да го завршиме овој дел. Колизија помеѓу горната и долната страна на екранот и топчето, ограничување на движењето на играчите и колизија помеѓу играчите и топчето.

Прво ќе го средиме топчето. Отворете ја Ball.cs класата и додадете го следниот метод:

public void CheckWallCollision()
{
	if (Position.Y < 0)
	{
		Position.Y = 0;
		Velocity.Y *= -1;
	}
	if (Position.Y + Texture.Height > Game1.ScreenHeight)
	{
		Position.Y = Game1.ScreenHeight - Texture.Height;
		Velocity.Y *= -1;
	}
}

 

Овој метод прво проверува дали топчето излегува од горната страна. Доколку излегува од горната страна, ја сетираме Y координатата на 0 и ја променуваме насоката на брзината (со ова добиваме одбивање од горната страна). Во вториот if-тест се проверува истото за долната страна меѓутоа приметувате дека треба да се искористи и висината на самата текстура бидејќи позицијата се наоѓа на горниот лев агол на самото топче.

Овој метод сега ќе го повикаме во Move методот на топчето. Додадете го следниот метод во Ball.cs класата:

public override void Move(Vector2 amount)
{            
	base.Move(amount);
	CheckWallCollision();
}

 

Овој метод прво го повикува Move методот од GameObject (класата од кој што наследува) и потоа го повикува методот за проверка на колизии со горната и долната страна на екранот.

Пред да тестираме, ќе додадеме неколку линии за да се ресетира топчето доколку излезе од левата или десната страна на екранот.

Отворете ја Game1.cs класата и внесете го следниот код веднаш по процесирањето на Touch инпутот:

player1.Move(player1TouchVelocity);
player2.Move(player2TouchVelocity);

if (ball.Position.X + ball.Texture.Width < 0)
{
	ball.Launch(BALL_START_SPEED);
}

if (ball.Position.X > ScreenWidth)
{
	ball.Launch(BALL_START_SPEED);
}

 

Првиот if-тест проверува дали топчето излегло од левата страна и доколку да, повторно го лансира од центарот на екранот. Вториот if-тест проверува дали топчето излегло од десната страна.

Притиснете F5 или Debug –> Start Debugging за да ги тестирате последните промени. Ќе забележите дека сега топчето се одбива од горната и долната страна, а кога излегува од левата или десната страна се ресетира повторно на центарот на екранот.

Следниот чекор е да се ограничи движењето на играчите. Отворете ја Player.cs класата и додадете ја следната библиотека:

using Microsoft.Xna.Framework;

Потоа, додадете го следниот метод:

public override void Move(Vector2 amount)
{
	base.Move(amount);
	if (Position.Y <= 0)
		Position.Y = 0;
	if (Position.Y + Texture.Height >= Game1.ScreenHeight)
		Position.Y = Game1.ScreenHeight - Texture.Height;
}

 

Како и за топчето, најпрво се повикува Move методот од GameObject (класата од која што наследува) потоа се проверува дали играчот се наоѓа на горната граница  и се ограничува позицијата доколку играчот се обиде да излезе од екранот. Истото се случува и во вториот if-тест каде што се проверува дали играчот сака да излезе од долната страна.

Притиснете F5 или Debug –> Start Debugging и тестирајте ги последните промени. Ќе забележите дека сега играчот не смее да го напушти екранот.

Останува уште последниот дел од колизиите, колизија помеѓу топчето и играчите.

Отворете ја GameObject.cs класата и додадете го следното Property:

public Rectangle Bounds
{
	get { return new Rectangle((int)Position.X, (int)Position.Y, Texture.Width, Texture.Height); }
}

 

Ова Property враќа правоаголник со границите на објектите (играчите и топчето во нашиот случај).

Во истата класа (GameObject.cs) додадете го следниот метод:

public static bool CheckPaddleBallCollision(Player player, Ball ball)
{
	if (player.Bounds.Intersects(ball.Bounds))
		return true;
	return false;
}

 

Овој метод враќа булеан (true или false), а како параметри зема играч и топче. Со користење на Bounds Property-то за добивање на правоаголникот околу објектите се проверува дали правоаголникот на играчот се пресекува со правоаголникот на топчето. Доколку постои пресек, враќаме true. Во спротивост враќаме false.

Овој метод е статичен и може да се повика преку GameObject.CheckPaddleBallCollision(…);

Отворете ја Game1.cs класата и додадете ја следната библиотека:

using System;

 

Потоа во Update методот, веднаш после Touch движењето и пред проверката за повторно лансирање на топчето внесете ги линиите како што е покажано:

player1.Move(player1TouchVelocity);
player2.Move(player2TouchVelocity);

if (GameObject.CheckPaddleBallCollision(player1, ball))
{
	ball.Velocity.X = Math.Abs(ball.Velocity.X);
}

if (GameObject.CheckPaddleBallCollision(player2, ball))
{
	ball.Velocity.X = -Math.Abs(ball.Velocity.X);
}

if (ball.Position.X + ball.Texture.Width < 0)
{
	ball.Launch(BALL_START_SPEED);
}

 

Во четвртата линија забележувате се проверува колизија помеѓу првиот играч и топчето. Доколку постои колизија, брзината на топчето во X оската добива позитивна насока (позитивна насока во случајот значи надесно). Потоа се проверува колизија помеѓу вториот играч и топчето. Доколку постои колизија, се дава негативна вредност на брзината во X оската.

Притиснете F5 или Debug –> Start Debugging за да тестирате.

Со овој чекор го завршуваме третиот дел од овој серијал. Доколку нешто не ви е јасно, или заглавивте некаде, оставете коментар подолу. За да го превземете мојот Solution од овој дел, притиснете тука.

Напомена: Ќе треба да ги сетирате референците до MonoGame за да го тестирате мојот Solution.

Во наредниот, последен дел од овој серијал, ќе ја дотераме играта. Ќе додадеме ‘вртење’ на топчето кога играчот ќе го мава, ќе додадеме индикатори за поени, звуци и слично.

Advertisements