Adventures in Symfony
Using Open Source Software to Make an MMO Game
Margaret Staples
Adventures in Symfony
Using Open Source Software to Make an MMO Game
Margaret Staples
Hello, nice to meet you!
I'm Margaret Staples
Hello, nice to meet you!
I'm Margaret Staples
I've been developing a Social Strategy City-Builder RPG
game of awesomeness.
Overview
Planning
Core Systems Development
Player Testing
Feature Completion
Deployment
Planning
Planning
Inventory Design Info
Inventory Design Info
Locate, Categorize, Analyze.
Planning
Conceptualize Development
Conceptualize Development
“No Battle Plan Survives Contact With the Enemy”
Planning
Initial Database Design
Initial Database Design
Resources
Goods
Buildings
Items
Actions
Districts
Classes
Attributes
Units
Planning
Learn the Framework
"Learn" the "Framework"
Core Systems
harvesting, processing, crafting, training, building, actions, leveling
Core Systems
Initial Database Content
Initial Database Content
Install Doctrine Fixtures Bundle
Create Entities
> composer require doctrine/doctrine-fixtures-bundle
// src/Group/NameBundle/Entity/Resources.php
/**
* @ORM\Entity
* @ORM\Table(name="resources")
*/
> php app/console doctrine:schema:update --force
> php app/console doctrine:generate:entities Group/NameBundle/Entity/Resources
Core Systems
Initial Database Content
Initial Database Content
Fixtures and Generated Data
Google Docs
Design Documents
Fixture Spreadsheets
Export to CSV
// [ProjectDirectory]/src/[Group]/[Bundle]/Datafixtures/ORM/exports
Core Systems
Initial Database Content
Initial Database Content
// src/Group/NameBundle/DataFixtures/ORM/LoadResourcesData.php
namespace Group\NameBundle\DataFixtures\ORM;
use Doctrine\Common\DataFixtures\FixtureInterface;
use Doctrine\Common\Persistence\ObjectManager;
use Group\NameBundle\Entity\Resources;
class LoadResourcesData implements FixtureInterface
{
public function load(ObjectManager $manager)
{
$filename = __DIR__.'/exports/resources.txt';
$fd = fopen($filename, "r");
$contents = fread($fd, 10000);
fclose($fd);
$splitcontents = explode(',', $contents);
$counter=0;
foreach ($splitcontents as $each) {
if ($counter == 0) {
$resource = new Resources();
$resource->setName($each);
}
...
}
}
}
> php app/console doctrine:fixtures:load
Core Systems
Initial Database Content
Initial Database Content
// src/Group/NameBundle/Command/startOneCommand.php
namespace Group\NameBundle\Command;
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Symfony\Component\Console\Output\OutputInterface;
use ONN\BrunBundle\Entity\Resources;
class startOneCommand extends ContainerAwareCommand
{
protected function configure()
{
$this->setName('start:one')
->setDescription('setup the game server');
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$em = $this->getContainer()->get('doctrine')->getManager('default');
$output->writeln('step one process: generating Wilderness');
$this->generateWilderness();
$output->writeln('... complete');
}
}
> php app/console start:one
Core Systems
Expanded Interactions
Fundamentals
Login, Character Creation, Settlement Creation
Player (FOS UserBundle)
Lord Character
Hero Character
Settlement
*Facebook, Google+, Twitter, Steam
Core Systems
Initial Interactions
Initial Interactions
Harvest, Process, Construction
Core Systems
Initial Time Based Events
Initial Time Based Events
Harvest, Process, Construction
Timed Event Tables:
- EventMin
- EventHour
- EventDay
- EventWeek
Core Systems
Initial Time Based Events
Initial Time Based Events
Harvest, Process, Construction
// src/Group/NameBundle/Command/timeCommand.php
...
class timeCommand extends ContainerAwareCommand
{
protected function configure()
{
$this
->setName('time')
->setDescription('Triggers timed event processing')
->setDefinition(array(
new InputArgument(
'timer',
InputArgument::REQUIRED,
'timer name'
),
));
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$timer = $input->getArgument('timer');
}
}
> php app/console time days
Core Systems
Initial Time Based Events
Initial Time Based Events
Harvest, Process, Construction
#!/bin/bash
# scripts/brunday.bsh
logfile=~/logs/bruncycle.log
exec >> $logfile 2>&1
export PATH
echo "Brun Daily Cycle:" >> ~/logs/bruncycle.log
php ~/www/app/console time days
> crontab -e
0 0 1 * * ~/scripts/brunback.bsh
01 * * * * ~/scripts/brunday.bsh
01 01 * * * ~/scripts/brunweek.bsh
*/15 * * * * ~/scripts/brunhour.bsh
*/5 * * * * ~/scripts/checkcycle.bsh
Core Systems
Initial UI
Initial UI
Like a MUD with web forms
public function someFormAction(Request $request, $active = 'no')
{
$form_name = "someForm";
$form_path = "some_form";
$form = $this->createFormBuilder(null)
...
->getForm();
return $this->render('GroupNameBundle:None:formFragment.html.twig', [
'form' => $form->createView(),
'form_name'=>$form_name,
'form_path'=>$form_path
]);
}
Core Systems
Initial UI
Initial UI
Like a MUD with web forms
<script>
function formSubmit(path,form_name) {
$.post( path,
$('#form_'+form_name).serialize(),
function(data){
$('#container_'+form_name).empty().append(data);
}
);
return false;
}
</script>
<div id="container_{{ form_name }}">
<form id='form_{{ form_name }}' method="post"
{{ form_enctype(form) }} name="{{ form_name }}" >
{{ form_widget(form) }}
<input id="{{ form_name }}"
onclick="formSubmit('{{ path( form_path, { 'active': 'yes' }) }}','{{ form_name }}')"
value='Submit' class='btn btn-small btn-custom' />
</form>
</div>
Core Systems
First Exposure to "Community"
First Exposure to "Community"
Core Systems
Expanded Interactions
Expanded Interactions
Forums
Crafting
Training
Actions
Leveling
Ranking
Local Markets
Global Market
Game Market
Fealty
Equipping
Friendship
Enmity
Marriage
Handfasting
Divorce
Dueling
Diplomacy
Scenarios
Armies
Player Testing
dissolution of assumptions
Player Testing
Players Do Things Differently
Players . . .
Do Things Differently
Create Unpredicted Strain
Provide Design Clarity
Player Testing
Communicating with Players
Communicating with Players
Bug Report Form
Game Forum Posts
Game Forum PMs
Other Forum Posts
Emails
Tweets
Skype
Google+ Chat
Steam Chat
Player Testing
Prioritizing Reports and Requests
Prioritizing Reports and Requests
Feature Completion
Feature Completion
Organization Concerns
Organization Concerns
What Goes Where, With Access to What
Controllers
Services
Repositories
Commands
Feature Completion
Peripheral Features
"Peripheral" Features
Require as Much or More as "Core" Features
JIT Design Completion:
Less Flexible, Harder Refactors
Feature Completion
Deliberate Testing
Deliberate Testing
Feature Completion
Time Based Events: Redux
Time Based Events: Redux
RabbitMQ
> composer require oldsound/rabbitmq:1.*
# app/config/config.yml
old_sound_rabbit_mq:
connections:
default:
host: 'localhost'
port: 5672
user: %rabbit_user%
password: %rabbit_password%
...
producers:
queue_task:
...
consumers:
queue_task:
...
Feature Completion
Time Based Events: Redux
Time Based Events: Redux
RabbitMQ
# src/Group/NameBundle/Consumer/ProcessQueueConsumer.php
...
class ProcessQueueConsumer implements ConsumerInterface
{
public function __construct(EntityManager $entityManager, $kernel)
{
...
}
public function execute(AMQPMessage $msg)
{
$kernel = $this->kernel;
$command = $msg->body;
...
$path = str_replace('scripts','','php '. $kernel->getRootDir() . '/console ' . $command);
$process = new Process($path);
$process->start();
...
return $process->getOutput();
}
}
$command = 'update-act-access '.$char_id;
$this->get('old_sound_rabbit_mq.queue_task_producer')->publish($command);
Feature Completion
Time Based Events: Redux
Time Based Events: Redux
#!/bin/bash
# scripts/checkworkers.bsh
NB_TASKS=15
SYMFONY_ENV="prod"
TEXT[0]="app/console rabbitmq:consumer -l 256 queue_task"
for text in "${TEXT[@]}"
do
NB_LAUNCHED=$(ps ax | grep "$text" | grep -v grep | wc -l)
TASK="/usr/bin/env php ${text} --env=${SYMFONY_ENV}"
for (( i=${NB_LAUNCHED}; i<${NB_TASKS}; i++ ))
do
echo "$(date +%c) - Launching a new consumer"
nohup $TASK > consumer_$i.log &
done
done
RabbitMQ
Feature Completion
Reflecting Change for Active Players
Reflecting Change to Active Players
Feature Completion
API
API
Feature Completion
3rd Party Code Upgrades
3rd Party Code
Doctrine Fixtures
FOS User
FOS Facebook
FOS Twitter
Bitgandtter Google
Widop PHPBB
Oldsound RabbitMQ
Cboden Ratchet
Egeloen CKeditor
Avalanche123 Imagine
Bundles
APIs
Steam
Integrated Intsall
PHPBB
MediaWiki
SVG Avatars Generator
Deployment
Deployment
Estimates are Hard
Estimates are Hard
Deployment
Magic and Dreams
Magic and Dreams
Payments
Players
Settlement Servers
Interest
Decay
Adventures in Symfony
Using Open Source Software to Make an MMO Game
brunegame.com
Questions?
brunegame.com
Adventures in Symfony
Using Open Source Software to Make an MMO Game
brunegame.com
Margaret Staples
twitter: @dead_lugosi
freenode: deadlugosi
email: mstaples@aesopgames.com
Adventures in Symfony
By Margaret Staples
Adventures in Symfony
Ride along on a tour of my latest adventure! I will unpack my process for turning design ideas into game mechanics, talk about the tools that were used (including Symfony and other open source goodies like Twig and Doctrine), examine major roadblocks and how they were overcome, give a postmortem on key implementation decisions, and share the highlights of what I learned along the way.
- 3,551