You are not logged in.
Pages: 1
Topic closed
Hey, anyone out there a video game programmer? Or learning to become one?
I thought it'd be cool to open this subject to discuss all kinds of programming strategy focused on video games!
First we can all talk about our programming experience in that field;
I've started programming video games in Assembly on my Casio calculator back when I was in the lycee in achrafieh during my arabic and french classes and I totally fell in love with it, decided it was what I'll be doing for the rest of my life, and luckily enough I got a chance to get an education in that domain (real time interactive simulation) and programmed some games in C++ for NOA (nintendo of america); those were mostly console, therefor it was C++ mixed with the SDK of the specific console (Nintendo DS in my situation).
As much as i loved working at NOA I decided I needed to push my education a little bit further, so I'm now currently in Washington DC doing some university stuff.
PHP& MySQL have always fascinated me, I started learning PHP about 5 years ago during the PHP4 era, and I grew it with it when it came to OOP, then moved to PHP5 as one of their earliest testers back in july 2004.
Last november, I set a challenge to myself to try and "revolutionize" the way PHP is used nowadays in the video game industry, a lot of you here maybe know how many "Text-Based" games there are in PHP, games that take a couple of days to program, or can be programmed using pre written scripts. Games that really give PHP a bad name. So I decided to try and program a PHP browser MMORPG game based on a c++ architecture, by that I mean having the proper:
-Game loop
-Level loop
-classes for pretty much everything (physics, characters, enemy, AI, items, cities etc)
And most importantly for the game to be FULLY graphical, I didnt want any "html text" in my game. I was lucky to find 2 artists and 2 musicians who were willing to help out with the assets of the game for free, and since then its been hard work for all of us. But the efforts have paid off quite a bit, here are some screenshots of the current version of the game (still far from done, but its still pretty intresting to see):
First picture, just a random picture in the city:
This one is still in the 1st city, but in the jail :)
In one of the houses of the 1st city, demonstrating one of the things you can do to other players
What kind of RPG doesnt have a decent weapon shop? ;)
Conversation with an NPC(non playable character)
A -VERY- early screenshot of the battle system (temp background image, temp HUd etc)
Another very early screenshot of the 2nd city (just a sneak preview)
Another screenshot of the battle system, ignore my health in this image, I set myself to invincible while I was testing it so the rats kept bitting me :)
The 1st enemy killed :)
The next 2 pictures are just screenshots of your menu screen
Another very early picture of the hospital:
Now, some demonstrations of the weather system =)
Clear weather:
Rainy:
Foggy:
And here's a special something about what happens when you drink a lot of alcohol ;)
I'd just like to remind you by now that this is only a browser game using ONLY PHP & MySQL (the only flash is in a non-visible background frame running hte music), the reason we have such simplistic graphics is because of bandwidth issues, some of you may know how PNG's work, their size get bigger as their palette gets bigger aswell, so we decided to leave the graphics simple to a few colors to minimise our bandwidth issues.
And to finally to give you an idea of teh scale of the game, heres the world map:
http://www.laene.nl/Marc/worms/images/maps/WormsMap.PNG
You can see the multiple cities, quests area's, arena's, casino, and misc stuff on it =)
Anyways, for all of you video game developpers out there, share your experiences with game programming, there's always something to learn frome each other!
Last edited by Flamie (June 27 2006)
awsome .... i wish i could go into that domain.
i programmed couple of games tho. couple using engines and two others using OpenGL (on C). it's been a nice experience too bad never got the chance to work more on them.
It's nice to see you worked on this game ... i've been wondering why there isn't such games availabe...
wow amazing.
Reminds me of all quest games and the "larry" series on old PCs. I used to like these when I was a kid.
lol, Do you call this " gaming development "?
Grab a DirectX9 book and start building an engine!
Did you even bother reading my 1st post? I've developped games under DX3d, DX2d and console games. You obviously have no clue what you're talking about or you would know that programming a game has little to do with DirectX, and that DirectX cant be used with PHP, intsead it uses GD2. Then again I guess you didnt know that.
An exemple of a GD2 script:
<?
header("Content-type: image/png");
include('../config.php');
new connect;
$width = 550;
$height = 350;
$result = mysql_query("SELECT * FROM rpg_players WHERE id = '".$_GET['id']."'");
$player = mysql_fetch_array($result);
$pid = $player['id'];
$posx = $player['posx'];
$posy = $player['posy'];
$raceid = $player['race_id'];
$city_id = $player['city_id'];
$screen = $player['city_screen'];
$map_id = $player['map_id'];
$chests = $player['chests'];
$pm = $player['partymembers'];
if($pm != '')
$partymembers = explode('-', $pm);
$num = $_GET['count'];
for($i=0;$i<$num;$i++)
{
$var = "string$i";
$msgs[$i] =$_GET[$var];
}
//create main image
$temp = imagecreatetruecolor($width, $height);
//get city name
$city_name = mysql_result(mysql_query("SELECT name FROM rpg_cities WHERE id=$city_id"),0);
//get
$map_name = $city_name.".PNG";
//get path
$map_path = mysql_result(mysql_query("SELECT path from rpg_maps WHERE id=$map_id"),0);
//create image
$map = imagecreatefrompng($map_path);
//create player
$ava = imagecreatefrompng("/home/laene/public_html/Marc/worms/images/Player/Map".$raceid.".PNG");
$avaw = (int)(imagesx($ava) / 2);
$avah = (int)(imagesy($ava) / 2);
$white = imagecolorallocate($ava, 255, 255, 255);
imagecolortransparent($ava, $white);
// top left x and y of the viewport
$tlx = $posx - ($width/2);
$tly = $posy - ($height/2);
//Get players around you
$result2 = mysql_query("SELECT * FROM rpg_players WHERE map_id=$map_id AND posx > $tlx AND posx < $tlx + $width AND posy > $tly AND posy < $tly + $height AND id!=$pid");
$num2 = mysql_num_rows($result2);
//render players around
for($i=1; $i<=$num2; $i++)
{
$p = mysql_fetch_array($result2);
$p_race = $p["race_id"];
$px = $p["posx"];
$py = $p["posy"];
$ava2 = imagecreatefrompng("/home/laene/public_html/Marc/worms/images/Player/Map".$p_race.".PNG");
$ava2w = (int)(imagesx($ava2) / 2);
$ava2h = (int)(imagesy($ava2) / 2);
$white2 = imagecolorallocate($ava2, 255, 255, 255);
imagecolortransparent($ava2, $white);
imagecopymerge($map, $ava2, $px-$ava2w, $py-$ava2h, 0, 0, imagesx($ava2), imagesy($ava2), 100);
if($p['id'] == $partymembers[0] || $p['id'] == $partymembers[1])
{
//draw green elipse around him
$green = imagecolorallocate($map, 255, 255, 255);
imageellipse($map, $px, $py, $ava2w*2+5, $ava2h*2+5, $green);
}
}
//Get NPC's around
$result3 = mysql_query("SELECT * FROM rpg_npc WHERE map_id=$map_id AND posx > $tlx AND posx < $tlx + $width AND posy > $tly AND posy < $tly + $height");
$num3 = mysql_num_rows($result3);
//render NPC's around
for($i=1; $i<=$num3; $i++)
{
$npc = mysql_fetch_array($result3);
$px = $npc["posx"];
$py = $npc["posy"];
$sprite_id = $npc["sprite_id"];
$image = mysql_result(mysql_query("SELECT image FROM rpg_npc_sprites WHERE id=$sprite_id"),0);
$path = "/home/laene/public_html/Marc/worms/images/NPC/".$image;
$ava3 = imagecreatefrompng($path);
$ava3w = (int)(imagesx($ava3) / 2);
$ava3h = (int)(imagesy($ava3) / 2);
$white3 = imagecolorallocate($ava3, 255, 255, 255);
imagecolortransparent($ava3, $white3);
imagecopymerge($map, $ava3, $px-$ava3w, $py-$ava3h, 0, 0, imagesx($ava3), imagesy($ava3), 100);
}
//get chests around
$result4 = mysql_query("SELECT * FROM rpg_chests WHERE map_id=$map_id AND x > $tlx AND x < $tlx + $width AND y > $tly AND y < $tly + $height");
$num4 = mysql_num_rows($result4);
//render chests
for($i=1; $i<=$num4; $i++)
{
$chest = mysql_fetch_array($result4);
$cx = $chest['x'];
$cy = $chest['y'];
$cid = $chest['id'];
if(strstr($chests, $cid) == FALSE)
$cpath = "/home/laene/public_html/Marc/worms/images/Items/".$city_name."/".$city_name."ChestClosed.png";
else
$cpath = "/home/laene/public_html/Marc/worms/images/Items/".$city_name."/".$city_name."ChestOpen.png";
$chestim = imagecreatefrompng($cpath);
$white4 = imagecolorallocate($chestim, 255,255, 255);
imagecolortransparent($chestim, $white4);
imagecopymerge($map, $chestim, $cx, $cy, 0,0,15,15,100);
}
//render main character
imagecopymerge($map, $ava, $posx - $avaw, $posy - $avah, 0, 0, imagesx($ava), imagesy($ava), 100);
//Get Ovl path
$mapovlpath = str_replace(".PNG", "Ovl.PNG", $map_path);
//Check if Overlayer exists
if(file_exists($mapovlpath))
{
//if so, load the image and render it
$mapovl = imagecreatefrompng($mapovlpath);
$grey = imagecolorallocate($mapovl, 138, 179, 77);
imagecolortransparent($mapovl, $grey);
$ovlwidth = imagesx($mapovl);
$ovlheight = imagesy($mapovl);
imagecopymerge($map, $mapovl, 0, 0, 0, 0, $ovlwidth, $ovlheight, 100);
}
$mapbrightpath = str_replace(".PNG", "Bright.PNG", $map_path);
//Render 'time effect'
$bright = GetMapBrightness();
imagefilter($map, IMG_FILTER_BRIGHTNESS, $bright);
//check if 'bright' layer exists
if(file_exists($mapbrightpath))
{
//if so, load the image and render it
$mapbright = imagecreatefrompng($mapbrightpath);
$greybright = imagecolorallocate($mapbright, 138, 179, 77);
imagecolortransparent($mapbright, $greybright);
$brightwidth = imagesx($mapbright);
$brightheight = imagesy($mapbright);
imagecopymerge($map, $mapbright, 0, 0, 0, 0, $brightwidth, $brightheight, 100);
}
//if a TV screen
if($map_id == 16)
{
$numofscreen = 0;
$end = true;
while($end==true)
{
$imagename = $numofscreen + 1;
$filename = "/home/laene/public_html/Marc/worms/images/screens/".$imagename.".PNG";
if(file_exists($filename))
$numofscreen++;
else
$end=false;
}
$rand = rand(1,$numofscreen);
$pic = imagecreatefrompng("/home/laene/public_html/Marc/worms/images/screens/".$rand.".PNG");
$screen = imagecreatetruecolor(118, 51);
$pich = imagesx($pic);
$picw = imagesy($pic);
$startx = (int)(59-($pich/2));
imagecopymerge($screen, $pic, $startx, 0, 0, 0, $pich, $picw, 100);
imagecopymerge($screen, $pic, $startx, 0, 0, 0, $pich, $picw, 100);
imagecopymerge($map, $screen, 93, 9, 0, 0, 118, 51, 100);
}
//Render Weather effect
$weather = new Weather();
$type = $weather->GetWeather();
//fog is before cutting
if($type == 'fog')
{
if(strpos($map_path,$map_name) !== FALSE)
{
$fog = imagecreatefrompng("/home/laene/public_html/Marc/worms/images/Weather/Fog.PNG");
$mapw = imagesx($map);
$maph = imagesy($map);
$currx = 0;
$curry = 0;
while($currx <= $mapw)
{
while($curry <= $maph)
{
imagecopymerge($map, $fog, $currx, $curry, 0, 0, 550, 350, 65);
$curry += 350;
}
$currx += 550;
$curry = 0;
}
imagedestroy($fog);
}
}
//put the image into our frame
imagecopymerge($temp, $map, 0, 0, $tlx, $tly, $width, $height, 100);
//Rain effect
if(strpos($type, "rain") !== FALSE)
{
if(strpos($map_path,$map_name) !== FALSE)
{
$rainlayer = imagecreatefrompng("/home/laene/public_html/Marc/worms/images/Weather/RainLayer.PNG");
imagecopymerge($temp, $rainlayer, 0, 0, 0, 0, 550, 350, 50);
imagedestroy($rainlayer);
//1st quadrant
for($i=0;$i<30;$i++)
{
$rand = rand(1,4);
$randx = rand(0, 137);
$randy = rand(0, 174);
$drop = imagecreatefrompng("/home/laene/public_html/Marc/worms/images/Weather/".$type."2.PNG");
$white = imagecolorallocate($drop, 255, 255, 255);
imagecolortransparent($drop, $white);
imagecopymerge($temp, $drop, $randx, $randy, 0, 0, imagesx($drop), imagesy($drop), 63);
imagedestroy($drop);
}
//2nd quadrant
for($i=0;$i<30;$i++)
{
$rand = rand(1,4);
$randx = rand(138,274);
$randy = rand(0, 174);
$drop = imagecreatefrompng("/home/laene/public_html/Marc/worms/images/Weather/".$type."2.PNG");
$white = imagecolorallocate($drop, 255, 255, 255);
imagecolortransparent($drop, $white);
imagecopymerge($temp, $drop, $randx, $randy, 0, 0, imagesx($drop), imagesy($drop), 63);
imagedestroy($drop);
}
//3rd quadrant
for($i=0;$i<30;$i++)
{
$rand = rand(1,4);
$randx = rand(275,412);
$randy = rand(0, 174);
$drop = imagecreatefrompng("/home/laene/public_html/Marc/worms/images/Weather/".$type."2.PNG");
$white = imagecolorallocate($drop, 255, 255, 255);
imagecolortransparent($drop, $white);
imagecopymerge($temp, $drop, $randx, $randy, 0, 0, imagesx($drop), imagesy($drop), 63);
imagedestroy($drop);
}
//4th quadrant
for($i=0;$i<30;$i++)
{
$rand = rand(1,4);
$randx = rand(413,550);
$randy = rand(0, 174);
$drop = imagecreatefrompng("/home/laene/public_html/Marc/worms/images/Weather/".$type."2.PNG");
$white = imagecolorallocate($drop, 255, 255, 255);
imagecolortransparent($drop, $white);
imagecopymerge($temp, $drop, $randx, $randy, 0, 0, imagesx($drop), imagesy($drop), 63);
imagedestroy($drop);
}
//5th quadrant
for($i=0;$i<30;$i++)
{
$rand = rand(1,4);
$randx = rand(0,137);
$randy = rand(174, 350);
$drop = imagecreatefrompng("/home/laene/public_html/Marc/worms/images/Weather/".$type."2.PNG");
$white = imagecolorallocate($drop, 255, 255, 255);
imagecolortransparent($drop, $white);
imagecopymerge($temp, $drop, $randx, $randy, 0, 0, imagesx($drop), imagesy($drop), 63);
imagedestroy($drop);
}
//6th quadrant
for($i=0;$i<30;$i++)
{
$rand = rand(1,4);
$randx = rand(138,274);
$randy = rand(174, 350);
$drop = imagecreatefrompng("/home/laene/public_html/Marc/worms/images/Weather/".$type."2.PNG");
$white = imagecolorallocate($drop, 255, 255, 255);
imagecolortransparent($drop, $white);
imagecopymerge($temp, $drop, $randx, $randy, 0, 0, imagesx($drop), imagesy($drop), 63);
imagedestroy($drop);
}
//9th quadrant
for($i=0;$i<30;$i++)
{
$rand = rand(1,4);
$randx = $randx = rand(275,412);
$randy = rand(174, 350);
$drop = imagecreatefrompng("/home/laene/public_html/Marc/worms/images/Weather/".$type."2.PNG");
$white = imagecolorallocate($drop, 255, 255, 255);
imagecolortransparent($drop, $white);
imagecopymerge($temp, $drop, $randx, $randy, 0, 0, imagesx($drop), imagesy($drop), 63);
imagedestroy($drop);
}
//8th quadrant
for($i=0;$i<30;$i++)
{
$rand = rand(1,4);
$randx = rand(413,550);
$randy = rand(174, 350);
$drop = imagecreatefrompng("/home/laene/public_html/Marc/worms/images/Weather/".$type."2.PNG");
$white = imagecolorallocate($drop, 255, 255, 255);
imagecolortransparent($drop, $white);
imagecopymerge($temp, $drop, $randx, $randy, 0, 0, imagesx($drop), imagesy($drop), 63);
imagedestroy($drop);
}
}
}
//Output the Messages the Messages
$num = count($msgs);
for($i=1;$i<=$num;$i++)
{
$msgbox = imagecreatefrompng("/home/laene/public_html/Marc/worms/images/Hud/MessageBox.PNG");
$black = imagecolorallocate($msgbox, 0, 0, 0);
imagestring($msgbox, 3, 10, 15, $msgs[$i-1], $black);
imagecopymerge($temp, $msgbox, 25, 5 + ($i-1)*49, 0, 0, 501, 44, 70);
imagedestroy($msgbox);
}
//Check for drunkness
$drunkres = mysql_query("SELECT * FROM rpg_drunk WHERE player_id=$pid");
$drunknum = mysql_num_rows($drunkres);
if($drunknum == 1)
{
$drunk = mysql_fetch_object($drunkres);
$amplitude = 2*$drunk->level;
$frequency = 2*$drunk->level;
$wrap = 0;
$blurr = 10*$drunk->level;
//imagefilter($temp, IMG_FILTER_COLORIZE);
imageripple($temp,$amplitude,$frequency,$wrap);
}
//apply goggles
//imagefilter($temp, IMG_FILTER_COLORIZE,0,0,255);
//output the image
imagepng($temp);
function GetMapBrightness()
{
$hours = $_GET['hours'];
if($hours == 0)
return -50;
elseif($hours == 1)
return -42;
elseif($hours == 2)
return -33;
elseif($hours == 3)
return -25;
elseif($hours == 4)
return -17;
elseif($hours == 5)
return -8;
elseif($hours == 6)
return 0;
elseif($hours == 7)
return 8;
elseif($hours == 8)
return 17;
elseif($hours == 9)
return 25;
elseif($hours == 10)
return 33;
elseif($hours == 11)
return 42;
elseif($hours == 12)
return 50;
elseif($hours == 13)
return 42;
elseif($hours == 14)
return 33;
elseif($hours == 15)
return 25;
elseif($hours == 16)
return 17;
elseif($hours == 17)
return 8;
elseif($hours == 18)
return 0;
elseif($hours == 19)
return -8;
elseif($hours == 20)
return -17;
elseif($hours == 21)
return -25;
elseif($hours == 22)
return -33;
elseif($hours == 23)
return -42;
}
function imageripple($i,$amplitude,$frequency,$wrap) {
$width = imagesx($i);
$height = imagesy($i);
for ($y=0;$y<$height;$y++) {
for ($x=0;$x<$width;$x++) {
$dis_x = $x+(sin(deg2rad(($y/$height)*360)*$frequency)*$amplitude);
$dis_y = $y+(sin(deg2rad(($x/$width)*360)*$frequency)*$amplitude);
if ($wrap == 1) {
$dis_x = ($dis_x < 0) ? $dis_x + $width : $dis_x;
$dis_x = ($dis_x > $width) ? $dis_x - $width : $dis_x;
$dis_y = ($dis_y < 0) ? $dis_y + $height : $dis_y;
$dis_y = ($dis_y > $height) ? $dis_y - $height : $dis_y;
}
$displacement['x'][$x][$y] = $dis_x;
$displacement['y'][$x][$y] = $dis_y;
}
}
$i = imagedisplace($i,$displacement);
return $i;
}
function imagedisplace($i,$displacement) {
$width = imagesx($i);
$height = imagesy($i);
$temp = imagecreatetruecolor($width,$height);
imagecopy($temp,$i,0,0,0,0,$width,$height);
for ($y=0;$y<$height;$y++) {
for ($x=0;$x<$width;$x++) {
$rgb = imagecolorat($temp,$displacement['x'][$x][$y],$displacement['y'][$x][$y]);
$a = ($rgb >> 24) & 0xFF;
$r = ($rgb >> 16) & 0xFF;
$g = ($rgb >> 8) & 0xFF;
$b = $rgb & 0xFF;
$col = imagecolorallocatealpha($i,$r,$g,$b,$a);
imagesetpixel($i,$x,$y,$col);
}
}
imagedestroy($temp);
return $i;
}
?>
admin edit: added code tags.
^^^
This is all useless code.
And when I said DX9 I meant using it is features like pixel and vertex shaders.
I am outta here!
Oh! good luck on ur mission
or the rest of it
Dark_star.. i don't see why u made that comment
taking a quick look at the script.. i did not read it well.. toook a look
in the GetMapBrightness ()
maybe instead of using if hour = 1 return -42 ..else if ...
maybe u can store the values in a table in the database and do a select statement according to hour
that will imporve the code just a littlbe bit and makes it bit smaller and more flexible
and maybe you can make also combinations like if month = 1 and hour = 2 then brightness = x
if u do a table than u add a column for the month and the select becomes where month = @parm1 and hour =@param2
or you can do 1 .. 1.5 .. 1.7 without having to change code
just a suggestion
hope that was clear
I thought of that, but those values wont change I'm pretty sure of that. Its just the "brightness" factor to change from the day/night system =)
And darkstar, you make it seem like a game is graphics, you're dead wrong. most of the games released nowadays are webgames, a lesser percentage a console games, and an even lesser percentage are PC games, and an even lesser percentage are PC games that use DirectX. And like I said in my previous post, if you had any experience in game dev, you'd know taht your graphics engine (the one that will use DirectX, or OpenGL or w/e graphic lib you're using) is most usually one of the smallest because all it does is render the result of your other engines.
You totally come here and "diss" the way I defined game dev, talking like game dev just just 3D windows games. When a 3D game simply CANT be developped by just 1 programmer, 2 artists and 2 musicians. If you claim this is shit I seriously beg of you to link me to ONE browser game that has been developped in PHP and htat is fully graphical, and believe me I'll shut up.
Game Dev is not about how big your game is, nor about the "omg I use teh uber leet vertex shadow" its about how you structure you programming and link your classes together to make the game work. Like using a singleton game engine vs an object factory. Sometimes a text based game can be worth a lot more than a 3D game if the design is better thought...
Last edited by Flamie (June 28 2006)
/second Flamie
good luck bro :)
maybe u can store the values in a table in the database and do a select statement according to hour
that will imporve the code just a littlbe bit and makes it bit smaller and more flexible
You gotta throw in a database in there, you cant help it, huh ?
;)
Flamie have you considered using javascript and AJAX? It will be much more responsive. But there's an advantage in your approach, it's that it can run on older browser or handheld devices and the such.
I know the whole idea is to use PHP in a new way and it is very innovative.
BTW, since were talking about alternative PHP uses, there's a PHP-GTK distro out there, it lets you write fully graphical cross-platform apps in PHP.
This is designed to run over the web, right? I mean because you can also be planning to bundle it with PHP and distribute it as a standalone game.
Last edited by rolf (June 28 2006)
Well rolf, I've always been agains javascript because of its bad response.
I've been toying with ajax for the last few months, I've even written the engine to run fully on AJAX at one point, but it was very slow and choppy, it ran a lot better under PHP.
But I started implement some AJAX in a different way, for little things that make it a little better:
for exemple:
-Say you are standing somewhere and someone sends you an invitation to play Hide and Seek with him, you wont actually receive the invitation before moving or doing some sort of action (which will refresh the page). So I decided to implement ajax in stuff like that, so that when someone sends you an invite, or something happens without your knowledge it will tell you instead of waiting for you to refresh the page somehow.
I've never heard of PHP-GTK I'll look into it a bit later today :)
And yes, the game is meant to run in your browser (compatible with IE/FX/Opera)
@flamie: interesting project :)
@mir: ditto to what Rolf said
nice
Last edited by rolf (June 29 2006)
You gotta throw in a database in there, you cant help it, huh ?
;)
well .. ;) .. one small table lol ..doesn't count
Dark_star.. i don't see why u made that comment
And darkstar, you make it seem like a game is graphics, you're dead wrong
And where did you people read Darkstar ?
Sheesh I got flamed and I didnt even say a word
Anyways amazing stuff you got there flamie.
Damn sorry 8( I'm blaming Mir(sorry bro :)) for it But we both meant Dark_angel =)
And thanks for the compliment on the project
edit: currently working on the 1st quest since the art for it is done I'll post some more screenshots in a few days, maybe weeks
Last edited by Flamie (July 5 2006)
oops sorry dark_star..
u know i read dark..somehow i know ur nick by heart and since ur always in mind (w heik ossas)
i typed ur name ...
so we're cool ;)
w mitl ma bi2oul il matal .. kill il ha2 3al tolyen ..
Last edited by mir (July 5 2006)
lol no worries ;)
Pages: 1
Topic closed