Chain Monster

Started by
7 comments, last by bzroom 14 years, 5 months ago
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?
Advertisement
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.
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->00000000<-Tuns into this one:0000000000000000->0000000<-

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]
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.
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.
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).
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 = Body;<br>            }<br><br>            Body[<span class="cpp-literal"><span class="cpp-number">0</span></span>] = HeadPosition;<br>            SnakeT = <span class="cpp-literal"><span class="cpp-number">0</span></span>;<br>        }<br><br>        <span class="cpp-keyword">private</span> <span class="cpp-keyword">void</span> InterpolateSnakeChain(<span class="cpp-keyword">float</span> snakeT)<br>        {<br>            <span class="cpp-keyword">int</span> cnt = Body.Count;<br><br>            <span class="cpp-keyword">for</span> (<span class="cpp-keyword">int</span> i = cnt - <span class="cpp-literal"><span class="cpp-number">1</span></span>; i &gt;= <span class="cpp-literal"><span class="cpp-number">0</span></span>; –i)<br>            {<br>                Vector b = i == <span class="cpp-literal"><span class="cpp-number">0</span></span> ? HeadPosition + Velocity : Body;<br>                Vector diff = b - Body<span style="font-weight:bold;">;<br>                SmoothBody<span style="font-weight:bold;"> = Body<span style="font-weight:bold;"> + diff * snakeT;<br>            }<br><br>            SmoothHead = HeadPosition + Velocity * snakeT;<br>        }<br><br>        <span class="cpp-keyword">private</span> <span class="cpp-keyword">void</span> UpdateLogic(<span class="cpp-keyword">object</span> sender, EventArgs e)<br>        {<br>            <span class="cpp-keyword">if</span> (mode<span class="cpp-literal"><span class="cpp-number">1</span>.</span><span class="vb-function">Checked</span>)<br>            {<br>                <span class="cpp-comment">//omni direction classic</span><br>                Vector displacement = MousePos - HeadPosition;<br>                <span class="cpp-keyword">float</span> dLen = displacement.Normalize();<br><br>                <span class="cpp-keyword">if</span> (dLen &gt; BodyDiameter)<br>                {<br>                    IncSnakeChain();<br>                    displacement = MousePos - HeadPosition;<br>                    dLen = displacement.Normalize();<br>                }<br><br>                <span class="cpp-keyword">if</span> (dLen != <span class="cpp-literal"><span class="cpp-number">0</span></span>)<br>                    Velocity = displacement * BodyDiameter;<br>                <span class="cpp-keyword">else</span><br>                    Velocity = <span class="vb-function">new</span> Vector();<br><br>                SnakeT = dLen / BodyDiameter;<br>                InterpolateSnakeChain(SnakeT);<br>            } <br>            <span class="cpp-keyword">else</span> <span class="cpp-keyword">if</span> (mode<span class="cpp-literal"><span class="cpp-number">2</span>.</span><span class="vb-function">Checked</span>)<br>            {<br>                <span class="cpp-comment">//relaxation</span><br>                <span class="cpp-keyword">int</span> cnt = Body.Count;<br>                <span class="cpp-keyword">bool</span> metTolerance = <span class="cpp-literal">false</span>;<br>                <span class="cpp-keyword">const</span> <span class="cpp-keyword">int</span> maxSteps = <span class="cpp-literal"><span class="cpp-number">100</span></span>;<br>                <span class="cpp-keyword">const</span> <span class="cpp-keyword">float</span> errorTolerance = <span class="cpp-literal"><span class="cpp-number">0</span>.1f</span>;<br><br>                <span class="cpp-keyword">for</span> (<span class="cpp-keyword">int</span> steps = <span class="cpp-literal"><span class="cpp-number">0</span></span>; steps &lt; maxSteps; ++steps)<br>                {<br>                    metTolerance = <span class="cpp-literal">true</span>;<br>                    <span class="cpp-keyword">for</span> (<span class="cpp-keyword">int</span> i = <span class="cpp-literal"><span class="cpp-number">0</span></span>; i &lt; cnt; ++i)<br>                    {<br>                        <span class="cpp-keyword">bool</span> firstBody = (i == <span class="cpp-literal"><span class="cpp-number">0</span></span>);<br>                        Vector a = Body<span style="font-weight:bold;">;<br>                        Vector b = firstBody ? <span class="vb-function">new</span> Vector(MousePos) : Body;<br>                        Vector diff = b - a;<br>                        <span class="cpp-keyword">float</span> l = diff.Normalize();<br>                        <span class="cpp-keyword">float</span> error = BodyDiameter - l;<br>                        diff *= error;<br><br>                        Body<span style="font-weight:bold;"> -= diff;<br>                        SmoothBody<span style="font-weight:bold;"> = Body<span style="font-weight:bold;">;<br><br>                        <span class="cpp-keyword">if</span> (error &gt; errorTolerance) metTolerance = <span class="cpp-literal">false</span>;<br>                    }<br><br>                    <span class="cpp-keyword">if</span> (metTolerance) <span class="cpp-keyword">break</span>;<br>                }<br><br>                SmoothHead = MousePos;<br>            }<br>            <span class="cpp-keyword">else</span> <span class="cpp-keyword">if</span> (mode<span class="cpp-literal"><span class="cpp-number">3</span>.</span><span class="vb-function">Checked</span>)<br>            {<br>                <span class="cpp-comment">//smooth classic</span><br>                <span class="cpp-keyword">const</span> <span class="cpp-keyword">float</span> interval = <span class="cpp-literal"><span class="cpp-number">0</span>.5f</span>;<br>                <span class="cpp-keyword">float</span> dt = (<span class="cpp-keyword">float</span>)timer<span class="cpp-literal"><span class="cpp-number">1</span>.</span>Interval / <span class="cpp-literal"><span class="cpp-number">1000</span>.0f</span>;<br>                Time += dt;<br><br>                <span class="cpp-keyword">while</span> (Time &gt; interval)<br>                {<br>                    IncSnakeChain();<br><br>                    Velocity = <span class="vb-function">new</span> Vector(NextVelocity);<br><br>                    Time -= interval;<br>                }<br><br>                SnakeT += dt / interval;<br>                InterpolateSnakeChain(SnakeT);<br>            }<br>        }<br><br>        <span class="cpp-keyword">private</span> <span class="cpp-keyword">void</span> Form<span class="cpp-literal"><span class="cpp-number">1</span></span>_KeyDown(<span class="cpp-keyword">object</span> sender, KeyEventArgs e)<br>        {<br>            <span class="cpp-keyword">float</span> velocity = BodyDiameter;<br>            <span class="cpp-keyword">switch</span> (e.KeyCode)<br>            {<br>                <span class="cpp-keyword">case</span> Keys.Up: NextVelocity.X = <span class="cpp-literal"><span class="cpp-number">0</span></span>; NextVelocity.Y = -velocity; <span class="cpp-keyword">break</span>;<br>                <span class="cpp-keyword">case</span> Keys.Down: NextVelocity.X = <span class="cpp-literal"><span class="cpp-number">0</span></span>; NextVelocity.Y = velocity; <span class="cpp-keyword">break</span>;<br>                <span class="cpp-keyword">case</span> Keys.Left: NextVelocity.X = -velocity; NextVelocity.Y = <span class="cpp-literal"><span class="cpp-number">0</span></span>; <span class="cpp-keyword">break</span>;<br>                <span class="cpp-keyword">case</span> Keys.Right: NextVelocity.X = velocity; NextVelocity.Y = <span class="cpp-literal"><span class="cpp-number">0</span></span>; <span class="cpp-keyword">break</span>;<br>            }<br>        }<br><br>        <span class="cpp-keyword">private</span> <span class="cpp-keyword">void</span> viewPort_MouseMove(<span class="cpp-keyword">object</span> sender, MouseEventArgs e)<br>        {<br>            MousePos.X = e.X;<br>            MousePos.Y = e.Y;<br>        }<br><br>        <span class="cpp-keyword">void</span> Application_Idle(<span class="cpp-keyword">object</span> sender, EventArgs e)<br>        {<br>            viewPort.Invalidate();<br>        }<br><br>        <span class="cpp-keyword">private</span> <span class="cpp-keyword">void</span> viewPort_Resize(<span class="cpp-keyword">object</span> sender, EventArgs e)<br>        {<br>            backbuffer = <span class="vb-function">new</span> Bitmap(viewPort.Width, viewPort.Height);<br>        }<br><br>        <span class="cpp-keyword">private</span> <span class="cpp-keyword">void</span> viewport_Paint(<span class="cpp-keyword">object</span> sender, PaintEventArgs e)<br>        {<br>            <span class="cpp-keyword">if</span> (backbuffer != <span class="cpp-literal">null</span>)<br>            {<br>                Graphics g = Graphics.FromImage(backbuffer);<br>                g.Clear(Color.Black);<br><br>                <span class="cpp-keyword">foreach</span> (Vector v <span class="cpp-keyword">in</span> SmoothBody)<br>                    g.DrawEllipse(Pens.Red, v.X - BodyRadius, v.Y - BodyRadius<br>                        , BodyDiameter, BodyDiameter);<br><br>                g.DrawEllipse(Pens.Blue, SmoothHead.X - BodyRadius, SmoothHead.Y - BodyRadius<br>                    , BodyDiameter, BodyDiameter);<br><br>                e.Graphics.DrawImage(backbuffer, <span class="vb-function">new</span> Rectangle(<span class="cpp-literal"><span class="cpp-number">0</span></span>, <span class="cpp-literal"><span class="cpp-number">0</span></span>, backbuffer.Width,<br>                    backbuffer.Height), <span class="cpp-literal"><span class="cpp-number">0</span></span>, <span class="cpp-literal"><span class="cpp-number">0</span></span>, backbuffer.Width,<br>                    backbuffer.Height, GraphicsUnit.Pixel);<br>            }<br>        }<br><br>        <span class="cpp-keyword">private</span> <span class="cpp-keyword">void</span> mouseModeToolStripMenuItem_Click(<span class="cpp-keyword">object</span> sender, EventArgs e)<br>        {<br>            <span class="cpp-keyword">if</span> (sender == mode<span class="cpp-literal"><span class="cpp-number">1</span></span>)<br>            {<br>                mode<span class="cpp-literal"><span class="cpp-number">2</span>.</span><span class="vb-function">Checked</span> = !mode<span class="cpp-literal"><span class="cpp-number">1</span>.</span><span class="vb-function">Checked</span>;<br>                mode<span class="cpp-literal"><span class="cpp-number">3</span>.</span><span class="vb-function">Checked</span> = <span class="cpp-literal">false</span>;<br>            }<br>            <span class="cpp-keyword">else</span> <span class="cpp-keyword">if</span> (sender == mode<span class="cpp-literal"><span class="cpp-number">2</span></span>)<br>            {<br>                mode<span class="cpp-literal"><span class="cpp-number">1</span>.</span><span class="vb-function">Checked</span> = !mode<span class="cpp-literal"><span class="cpp-number">2</span>.</span><span class="vb-function">Checked</span>;<br>                mode<span class="cpp-literal"><span class="cpp-number">3</span>.</span><span class="vb-function">Checked</span> = <span class="cpp-literal">false</span>;<br>            }<br>            <span class="cpp-keyword">else</span> <span class="cpp-keyword">if</span> (sender == mode<span class="cpp-literal"><span class="cpp-number">3</span></span>)<br>            {<br>                mode<span class="cpp-literal"><span class="cpp-number">1</span>.</span><span class="vb-function">Checked</span> = !mode<span class="cpp-literal"><span class="cpp-number">3</span>.</span><span class="vb-function">Checked</span>;<br>                mode<span class="cpp-literal"><span class="cpp-number">2</span>.</span><span class="vb-function">Checked</span> = <span class="cpp-literal">false</span>;<br>            }<br>        }<br>    }<br>}<br><br><br></pre></div><!–ENDSCRIPT–><br><br><img src="http://kincaid05.googlepages.com/snake.PNG"><br><br>Edit: added a 3rd mode (mode2) which uses relaxation to achieve an effect of lightbringer's second video.<br><br><!–EDIT–><span class=editedby><!–/EDIT–>[Edited by - bzroom on November 15, 2009 10:39:26 PM]<!–EDIT–></span><!–/EDIT–>
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...
You're welcome, my pleasure.

This topic is closed to new replies.

Advertisement