Skip to main content

Part 3: Player Character

In this part, we'll create our player character with movement controls, animations, and physics integration.

What We'll Accomplish​

By the end of this part, you'll have:

  • A controllable player character
  • Basic movement with arrow keys or WASD
  • Sprite animations for idle, walking, and jumping
  • Physics-based movement using Box2D
  • Proper character collision detection

Prerequisites​

Step 1: Player Character Assets​

For this tutorial, we'll need the following sprite assets:

  • player_idle.png - Idle animation frames
  • player_run_1.png, player_run_2.png, player_run_3.png, player_run_4.png
    • Running animation frames
  • player_jump.png - Jumping frame
  • jump.wav - Jumping sound
  • land.wav - Landing sound

You can find these assets in the complete project reference.

Add these files to your Content folder and ensure they're set to "Content" build action.

Step 2: Create Player Class​

Create a new file called Player.cs:

using System;
using Cocos2D;
using Box2D.Dynamics;
using Box2D.Common;
using Box2D.Collision.Shapes;
using CocosDenshion;

namespace Platformer
{
public class Player : CCSprite
{
// Physics body
private b2Body _body;

// Movement parameters
private const float MOVE_SPEED = 5.0f;
private const float JUMP_FORCE = 7.0f;
private bool _canJump = false;
private int _jumpCount = 0;
private const int MAX_JUMPS = 2; // Allow double jump
private bool _isRunning = false;

// Animation states
private CCAnimation _idleAnimation;
private CCAnimation _runAnimation;
private CCAnimation _jumpAnimation;

public Player(b2World world) : base("player_idle")
{
// Create physics body
b2BodyDef bodyDef = new b2BodyDef();
bodyDef.type = b2BodyType.b2_dynamicBody;
bodyDef.fixedRotation = true; // Prevent rotation
bodyDef.allowSleep = false;

_body = world.CreateBody(bodyDef);

// Create fixture
b2PolygonShape shape = new b2PolygonShape();
// Make the collision box slightly smaller than the sprite
shape.SetAsBox(
ContentSize.Width * 0.4f / PhysicsHelper.PTM_RATIO,
ContentSize.Height * 0.45f / PhysicsHelper.PTM_RATIO);

b2FixtureDef fixtureDef = new b2FixtureDef();
fixtureDef.shape = shape;
fixtureDef.density = 1.0f;
fixtureDef.friction = 0.2f;
fixtureDef.restitution = 0.0f;

// Set collision filtering
fixtureDef.filter.categoryBits = PhysicsHelper.CATEGORY_PLAYER;
fixtureDef.filter.maskBits = PhysicsHelper.CATEGORY_PLATFORM | PhysicsHelper.CATEGORY_COLLECTIBLE;

_body.CreateFixture(fixtureDef);

// Add foot sensor for jump detection
b2PolygonShape footShape = new b2PolygonShape();
footShape.SetAsBox(
ContentSize.Width * 0.3f / PhysicsHelper.PTM_RATIO,
0.1f / PhysicsHelper.PTM_RATIO,
new b2Vec2(0, -ContentSize.Height * 0.45f / PhysicsHelper.PTM_RATIO),
0);

b2FixtureDef footFixtureDef = new b2FixtureDef();
footFixtureDef.shape = footShape;
footFixtureDef.isSensor = true;

b2Fixture footSensor = _body.CreateFixture(footFixtureDef);
footSensor.UserData = new FootSensorUserData(this);

// Load animations
LoadAnimations();
}

private void LoadAnimations()
{
// In a real game, you would load animation frames
// For this tutorial, we'll use placeholder logic

_idleAnimation = new CCAnimation();
// Add frames to animation
_idleAnimation.AddSpriteFrameWithFileName("player_idle");
_idleAnimation.DelayPerUnit = 0.2f;

_runAnimation = new CCAnimation();
// Add multiple frames for run animation
_runAnimation.AddSpriteFrameWithFileName("player_run_1");
_runAnimation.AddSpriteFrameWithFileName("player_run_2");
_runAnimation.AddSpriteFrameWithFileName("player_run_3");
_runAnimation.AddSpriteFrameWithFileName("player_run_4");
_runAnimation.DelayPerUnit = 0.1f;

_jumpAnimation = new CCAnimation();
_jumpAnimation.AddSpriteFrameWithFileName("player_jump");
_jumpAnimation.DelayPerUnit = 0.1f;
}

public void Update(float dt)
{
// Update sprite position based on physics body
Position = PhysicsHelper.ToCocosVector(_body.Position);

// Check if player fell off the screen
if (Position.Y < -100)
{
// Reset position
_body.SetTransform(new b2Vec2(100 / PhysicsHelper.PTM_RATIO, 300 / PhysicsHelper.PTM_RATIO), 0);
_body.LinearVelocity = b2Vec2.Zero;
}
}

public void MoveLeft()
{
_body.LinearVelocity = new b2Vec2(-MOVE_SPEED, _body.LinearVelocity.y);

// Flip sprite to face left
FlipX = true;

// Play run animation if on ground
if (_canJump && _jumpCount == 0 && !_isRunning)
{
_isRunning = true; // Set running state
RunAction(new CCRepeatForever(new CCAnimate(_runAnimation)));
}
}

public void MoveRight()
{
_body.LinearVelocity = new b2Vec2(MOVE_SPEED, _body.LinearVelocity.y);

// Flip sprite to face right
FlipX = false;

// Play run animation if on ground
if (_canJump && _jumpCount == 0 && !_isRunning)
{
_isRunning = true; // Set running state
RunAction(new CCRepeatForever(new CCAnimate(_runAnimation)));
}
}

public void StopMoving()
{
_body.LinearVelocity = new b2Vec2(0, _body.LinearVelocity.y);

// Play idle animation if on ground
if (_canJump && _jumpCount == 0)
{
_isRunning = false; // Stop running animation
StopAllActions();
RunAction(new CCRepeatForever(new CCAnimate(_idleAnimation)));
}
}

public void Jump()
{
if (_canJump && _jumpCount < MAX_JUMPS)
{
_body.LinearVelocity = new b2Vec2(_body.LinearVelocity.x, JUMP_FORCE);
_jumpCount++;
_canJump = (_jumpCount < MAX_JUMPS);

_isRunning = false; // Stop running animation when jumping
// Play jump animation
StopAllActions();
RunAction(new CCAnimate(_jumpAnimation));

// Play jump sound
PlayJumpSound();
}
}

public void SetCanJump(bool canJump)
{
if (canJump && !_canJump)
{
// Player just landed
_jumpCount = 0;

// Play landing sound
PlayLandSound();

// Play idle or run animation based on horizontal velocity
StopAllActions();

if (Math.Abs(_body.LinearVelocity.x) > 0.1f)
{
RunAction(new CCRepeatForever(new CCAnimate(_runAnimation)));
}
else
{
RunAction(new CCRepeatForever(new CCAnimate(_idleAnimation)));
}
}

_canJump = canJump;
}

private void PlayJumpSound()
{
CCSimpleAudioEngine.SharedEngine.PlayEffect("jump");
}

private void PlayLandSound()
{
CCSimpleAudioEngine.SharedEngine.PlayEffect("land");
}

// User data for foot sensor
public class FootSensorUserData
{
public Player Player { get; private set; }

public FootSensorUserData(Player player)
{
Player = player;
}
}

}
}

Step 3: Integrating Player into Game​

Update your GameLayer.cs to add the player, handle keyboard input & load sounds:

using System;
using System.Collections.Generic;
using Cocos2D;
using Box2D.Dynamics;
using Box2D.Common;
using Box2D.Dynamics.Contacts;
using Box2D.Collision;
using Microsoft.Xna.Framework.Input;
using CocosDenshion;

namespace Platformer
{
public class GameLayer : CCLayer
{
// Box2D world
private b2World _world;

// Game objects
private Player _player;

// Input state
private bool _isLeftPressed;
private bool _isRightPressed;
private bool _isJumpPressed;

private int _score = 0;
private CCLabelTTF _scoreLabel;
private CCMenuItemLabel _restartButton;

public GameLayer()
{
// Initialize physics world with gravity
_world = new b2World(new b2Vec2(0, -10.0f));

// Create level
CreateLevel();

// Load sounds
CCSimpleAudioEngine.SharedEngine.PreloadEffect("jump");
CCSimpleAudioEngine.SharedEngine.PreloadEffect("land");

ScheduleUpdate();
}

private void CreateLevel()
{
// Get visible area size
CCSize visibleSize = CCDirector.SharedDirector.WinSize;

// Create background
CCSprite background = new CCSprite("background");
background.Position = new CCPoint(visibleSize.Width / 2, visibleSize.Height / 2);
background.Scale = Math.Max(visibleSize.Width / background.ContentSize.Width,
visibleSize.Height / background.ContentSize.Height);
AddChild(background, -1);

// Create player
_player = new Player(_world);
_player.Position = new CCPoint(100, 300);
AddChild(_player);

// Create score label
_scoreLabel = new CCLabelTTF($"Score: {_score}", "MarkerFelt", 22);
_scoreLabel.Position = new CCPoint(100, visibleSize.Height - 30);
_scoreLabel.Color = CCColor3B.Black;
AddChild(_scoreLabel, 10);

// Create restart button
CCLabelTTF restartLabel = new CCLabelTTF("Restart", "MarkerFelt", 22);
restartLabel.Color = CCColor3B.Black;
_restartButton = new CCMenuItemLabel(restartLabel, RestartGame);
_restartButton.Position = new CCPoint(visibleSize.Width - 100, visibleSize.Height - 30);

CCMenu menu = new CCMenu(_restartButton);
menu.Position = CCPoint.Zero;
AddChild(menu, 10);
}

private void RestartGame(object sender)
{
// Reset score
_score = 0;

RemoveAllChildren();
CreateLevel();
}

public void IncreaseScore(int points)
{
_score += points;
_scoreLabel.Text = $"Score: {_score}";
}

public override void Update(float dt)
{
// Update physics world
_world.Step(dt, 8, 3);

// Update player movement based on input
if (_isLeftPressed)
_player.MoveLeft();
else if (_isRightPressed)
_player.MoveRight();
else
_player.StopMoving();

if (_isJumpPressed)
_player.Jump();

// Update all game objects
_player.Update(dt);

base.Update(dt);

// Handle keyboard state every frame
HandleInput();
}

public void HandleInput()
{
// Reset input state
_isLeftPressed = false;
_isRightPressed = false;
_isJumpPressed = false;

// Handle keyboard input
KeyboardState state = Keyboard.GetState();
if (state.IsKeyDown(Keys.Left))
_isLeftPressed = true;
if (state.IsKeyDown(Keys.Right))
_isRightPressed = true;
if (state.IsKeyDown(Keys.Space))
_isJumpPressed = true;
}
}
}

🎯 Checkpoint: Controllable Character​

At this point, you should have:

  • ✅ A player character that responds to keyboard input
  • ✅ Physics-based movement with Box2D
  • ✅ Basic sprite animation system
  • ✅ Character that can move left/right and has jump input

Test your game: Run the project and verify that:

  1. The player character appears on screen
  2. Arrow keys or WASD move the character left/right
  3. Spacebar or Up arrow makes the character jump
  4. The character sprite flips when changing direction

Troubleshooting​

Character not moving: Check that keyboard input events are properly set up and the physics body is created correctly.

Character moving too fast/slow: Adjust the MovementSpeed and JumpForce properties in the Player class.

Animations not working: Ensure sprite assets are properly added to the Content folder with "Content" build action.

Next Steps​

In Part 4: Platforms and Collision, we'll create platforms for the player to jump on and implement proper collision detection.

Complete Code Reference​

You can find the complete code for this part in the tutorial samples repository.