Sign in to follow this  

Chain Monster

This topic is 2954 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

I want to code a monster, that consists of many links. These enemies you can easy found in retro games. I have no better idea as every link has offset to the next and moves smoothly with a little pause. It is easy if chain moves in one direction. For example: oooooooooooooooooooo ooooooooooooooooooo o-> oooooooooooooooooo oo-> ooooooooooooooooo ooo-> So if i move first link, the other will be moved in the same direction. But it could be a little complexer. oooooooooooooooooo o-> o o <-oooooooooooooooooooo So all links above move right. 2 links move up And the other move left. How do i better implement it? Or may be you know any good tutorial or src?

Share this post


Link to post
Share on other sites
There are probably many ways to do this. I would do it like you suggested, using a doubly-linked list.


class SnakeLink:
SnakeLink next, previous
int x, y
int old_x, old_y



For the head you will want to go down the list and find if any SnakeLink occupies the same location as the one you want to move to, since that will be a game over condition. Then just update x,y, store old x,y, then delay a bit perhaps and update the next link (move to old x,y of previous link). Once in a while append a new link at the end.

Share this post


Link to post
Share on other sites
In addition to lightbringer's approach, there's another method that's pretty easy to use in some simple situations. This works best for console-based text games or others that don't rely on the individual "links" moving independently, so it may not work for you. I have adapted it to more complex cases with some success, though.

Consider the following scenario:
This chain:
000000000000000->
0
0
000000<-
Tuns into this one:
0000000000000000->
0
0
00000<-

What's the difference? The only thing that has changed is that the very end of the chain has been "dropped," and the head of the chain "grew" in the direction of travel. So, rather than moving every piece along the chain to occupy the space of the previous link, you can simply take the tail piece and move it to the head. To "grow" the monster, skip the drop step and just add one to the front. When animated, it'll look just like any snake or caterpillar game, though, as I noted before, it won't work if you want each link to move separately. It's far easier, though. [smile]

Share this post


Link to post
Share on other sites
Is this going to be a 2D or 3D game? Are the chains a 2D effect, or actual objects? Maybe I'm over thinking it, but if it's just a snake game, then the suggested solution seems great. I think moving the tail piece to the where the new head piece would be the fastest way to do it.

Share this post


Link to post
Share on other sites
2lightbringer:
Thank you for the answer,
i am doing right so and it works. But there is one problem here. If i move every link to old position, all snake tends to one point.
So: video 1
If i make a minimal offset between 2 links, it moves in a steps.
Here i have a problem, that my movement is to discrete.
So: video 2
If i don't use this minimal offset, i have a problem with length.
If i could make the snake go smoothly, but without any 2 links are crossed, it would be exact what i need.
this is a key codesnippet of my movement:

public void MoveTo(Vector2 dest)
{
Vector2 old_pos = new Vector2(pos.X, pos.Y);
pos = dest;
if (nextLink != null)
{

if (Vector2.Distance(nextLink.pos, old_pos) > 64)
nextLink.MoveTo(old_pos);
}

}








This method is called for a head. And head moves all other links. Exacter every link moves the next one. The IF-Condition is responsible for this minimal distance.

2jouley:
Thank you. Although this method is not what i am looking for, but is interesting and pretty good for if i would make a snake like on Nokia 3310=)

2M4573R:
Thank you for your answer. I am making 2d game, but it must not be so discrete as a real old Snake. The Snake must moves smoothly. So moving a tale piece is not what i need. Links are objects in my implementation.

P.S. I would be grateful for any help ^^
P.P.S. Sry for long time between posts, unfortunately i have only time on Weekend to program my game.
P.P.P.S. A simple Project for MVS Express 2008 in C# is available here LinkBasedSnake.

Share this post


Link to post
Share on other sites
I think your second video is close to how it's often done, and is what I imagined, because snake games are often based on a grid. See many of the examples here. But it seems what you had in mind was along the lines of this one, which looks pretty amazing to me, especially considering it's a flash game.

If you want smooth movement, just moving links to the old position won't be enough of course. You can try moving the link immediately but only move it a small fixed amount (same speed as the head) along the vector pointing from this link to the previous one counting from the head. In your code it would be something like scale*normalize(previousLink.pos - this.pos). Another way could be to use joints to keep the links connected (but writing a physics solver for a snake game is surely not what you had in mind :D).

Share this post


Link to post
Share on other sites
This is how i interpreted your question. The snake still increments like the classic snake, but rather than it appearing like the tail segment just drops off, the segments interpolate to the new position.

Click "mouse mode" to make it continuous like your videos. Unclick mouse mode to make it rectangular like the classic snake game:

You'll need VS C# to compile it, or you can just run the executable under the debug folder.

http://kincaid05.googlepages.com/SnakeDemo.zip


using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;

namespace Graphics_Framework
{

public partial class Form1 : Form
{
Vector HeadPosition = new Vector(), SmoothHead = new Vector();
List<Vector> Body = new List<Vector>();
List<Vector> SmoothBody = new List<Vector>();
float Time = 0, SnakeT = 0;
float BodyRadius = 10;
float BodyDiameter = 20;
Vector Velocity = new Vector(20, 0), NextVelocity = new Vector(20,0);
Vector MousePos = new Vector();
Bitmap backbuffer;


public Form1()
{
InitializeComponent();
Application.Idle += new EventHandler(Application_Idle);

HeadPosition.Y = this.Height / 2;

for (int i = 0; i < 10; ++i)
{
Body.Add(new Vector(HeadPosition));
SmoothBody.Add(new Vector(HeadPosition));
}

viewPort_Resize(null, null);
}

private void IncSnakeChain()
{
//Used for classic and omni classic
//move the snake forward one segment
HeadPosition += Velocity;

int cnt = Body.Count;
for (int i = cnt - 1; i > 0; --i)
{
Body[i] = Body[i - 1];
}

Body[0] = HeadPosition;
SnakeT = 0;
}

private void InterpolateSnakeChain(float snakeT)
{
int cnt = Body.Count;

for (int i = cnt - 1; i >= 0; --i)
{
Vector b = i == 0 ? HeadPosition + Velocity : Body[i - 1];
Vector diff = b - Body[i];
SmoothBody[i] = Body[i] + diff * snakeT;
}

SmoothHead = HeadPosition + Velocity * snakeT;
}

private void UpdateLogic(object sender, EventArgs e)
{
if (mode1.Checked)
{
//omni direction classic
Vector displacement = MousePos - HeadPosition;
float dLen = displacement.Normalize();

if (dLen > BodyDiameter)
{
IncSnakeChain();
displacement = MousePos - HeadPosition;
dLen = displacement.Normalize();
}

if (dLen != 0)
Velocity = displacement * BodyDiameter;
else
Velocity = new Vector();

SnakeT = dLen / BodyDiameter;
InterpolateSnakeChain(SnakeT);
}
else if (mode2.Checked)
{
//relaxation
int cnt = Body.Count;
bool metTolerance = false;
const int maxSteps = 100;
const float errorTolerance = 0.1f;

for (int steps = 0; steps < maxSteps; ++steps)
{
metTolerance = true;
for (int i = 0; i < cnt; ++i)
{
bool firstBody = (i == 0);
Vector a = Body[i];
Vector b = firstBody ? new Vector(MousePos) : Body[i - 1];
Vector diff = b - a;
float l = diff.Normalize();
float error = BodyDiameter - l;
diff *= error;

Body[i] -= diff;
SmoothBody[i] = Body[i];

if (error > errorTolerance) metTolerance = false;
}

if (metTolerance) break;
}

SmoothHead = MousePos;
}
else if (mode3.Checked)
{
//smooth classic
const float interval = 0.5f;
float dt = (float)timer1.Interval / 1000.0f;
Time += dt;

while (Time > interval)
{
IncSnakeChain();

Velocity = new Vector(NextVelocity);

Time -= interval;
}

SnakeT += dt / interval;
InterpolateSnakeChain(SnakeT);
}
}

private void Form1_KeyDown(object sender, KeyEventArgs e)
{
float velocity = BodyDiameter;
switch (e.KeyCode)
{
case Keys.Up: NextVelocity.X = 0; NextVelocity.Y = -velocity; break;
case Keys.Down: NextVelocity.X = 0; NextVelocity.Y = velocity; break;
case Keys.Left: NextVelocity.X = -velocity; NextVelocity.Y = 0; break;
case Keys.Right: NextVelocity.X = velocity; NextVelocity.Y = 0; break;
}
}

private void viewPort_MouseMove(object sender, MouseEventArgs e)
{
MousePos.X = e.X;
MousePos.Y = e.Y;
}

void Application_Idle(object sender, EventArgs e)
{
viewPort.Invalidate();
}

private void viewPort_Resize(object sender, EventArgs e)
{
backbuffer = new Bitmap(viewPort.Width, viewPort.Height);
}

private void viewport_Paint(object sender, PaintEventArgs e)
{
if (backbuffer != null)
{
Graphics g = Graphics.FromImage(backbuffer);
g.Clear(Color.Black);

foreach (Vector v in SmoothBody)
g.DrawEllipse(Pens.Red, v.X - BodyRadius, v.Y - BodyRadius
, BodyDiameter, BodyDiameter);

g.DrawEllipse(Pens.Blue, SmoothHead.X - BodyRadius, SmoothHead.Y - BodyRadius
, BodyDiameter, BodyDiameter);

e.Graphics.DrawImage(backbuffer, new Rectangle(0, 0, backbuffer.Width,
backbuffer.Height), 0, 0, backbuffer.Width,
backbuffer.Height, GraphicsUnit.Pixel);
}
}

private void mouseModeToolStripMenuItem_Click(object sender, EventArgs e)
{
if (sender == mode1)
{
mode2.Checked = !mode1.Checked;
mode3.Checked = false;
}
else if (sender == mode2)
{
mode1.Checked = !mode2.Checked;
mode3.Checked = false;
}
else if (sender == mode3)
{
mode1.Checked = !mode3.Checked;
mode2.Checked = false;
}
}
}
}






Edit: added a 3rd mode (mode2) which uses relaxation to achieve an effect of lightbringer's second video.

[Edited by - bzroom on November 15, 2009 10:39:26 PM]

Share this post


Link to post
Share on other sites
2lightbringer
Oh, good snake-site, thank you.

Yeah, snake games are really oftem grid-based. But its not my case, actually i am trying to make a shmup))) And such type of monsters make gameplay more interesting.

Yes, i think so too. But now i have a working prorotype from bzroom.

Joints are of course a solution, but doing it just for snake is a big overhead.

2bzroom
I am really very grateful to you. This is exact what i need! Thank you very very much^^!!!11 Great job!
I go home now to view your source and implement it...

Share this post


Link to post
Share on other sites

This topic is 2954 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this