Implement Touch‑Controlled Movement, Directional Walking Animations, and Physics Collisions for a Hero Character in Cocos Creator
This guide demonstrates how to enable touch‑based movement for a hero character, add directional walking animations, integrate animation logic, and implement physics‑based collision detection with walls in Cocos Creator, including code snippets for event handling, animation state management, and physics component setup.
1. Enable Touch‑Based Movement for the Hero
In the hero script's onLoad method we store the node's initial position and register touch event listeners on the node itself (instead of its parent) so that the hero moves only when it is directly touched.
onLoad: function() {
this.nodePos = this.node.getPosition();
this.node.on(cc.Node.EventType.TOUCH_MOVE, this.onTouchMove, this);
this.node.on(cc.Node.EventType.TOUCH_END, this.onTouchEnd, this);
this.node.on(cc.Node.EventType.TOUCH_CANCEL, this.onTouchCancel, this);
},When a touch ends we update this.nodePos to the current position.
onTouchEnd () {
this.nodePos = this.node.getPosition();
},
onTouchCancel: function () {
this.nodePos = this.node.getPosition();
},During onTouchMove we calculate the delta between the start location and the current location, then apply the delta to the stored position while clamping the hero within the screen bounds.
onTouchMove (event) {
var self = this;
var touches = event.getTouches();
var oldPos = self.node.parent.convertToNodeSpaceAR(touches[0].getStartLocation());
var newPos = self.node.parent.convertToNodeSpaceAR(touches[0].getLocation());
var subPos = newPos.sub(oldPos);
self.node.x = self.nodePos.x + subPos.x;
self.node.y = self.nodePos.y + subPos.y;
// clamp to screen bounds
var minX = -self.node.parent.width/2 + self.node.width/2;
var maxX = Math.abs(minX);
var minY = -self.node.parent.height/2 + self.node.height/2;
var maxY = Math.abs(minY);
var nPos = self.node.getPosition();
if (nPos.x < minX) nPos.x = minX;
if (nPos.x > maxX) nPos.x = maxX;
if (nPos.y < minY) nPos.y = minY;
if (nPos.y > maxY) nPos.y = maxY;
self.node.setPosition(nPos);
},2. Add Directional Walking Animations
Drag the hero sprite sheet into the scene hierarchy under the map node and rename it to hero . Create a new Hero script and bind it to the hero node, positioning the hero at the maze centre.
Open the Animation editor, add an Animation component to the hero node, and create a new animation clip (e.g., heroUp ) by dragging frames 1‑0 to 1‑5 onto the timeline. Set the clip to Loop and save.
Repeat the process for the remaining three directions, creating clips heroDown , heroLeft , and heroRight with the appropriate frame ranges (e.g., 0‑0 ‑ 0‑4 for down, 2‑0 ‑ 2‑4 for left, 3‑0 ‑ 3‑4 for right). Remove any extra frames such as 1‑5 from the upward clip.
3. Integrate Animation Logic in the Script
In the hero script we declare animationState and obtain the cc.Animation component in onLoad :
onLoad: function() {
this.animationState = '';
this.heroAnimation = this.node.getComponent(cc.Animation);
// other initialisation …
},A helper setAnimationState changes the state only when it differs from the current one and plays the corresponding clip.
setAnimationState (animationState) {
if (this.animationState == animationState) return;
this.animationState = animationState;
this.heroAnimation.play(this.animationState);
},During onTouchMove we compute the X/Y delta to decide the movement direction and call setAnimationState with the appropriate clip name ( heroUp , heroDown , heroLeft , heroRight ).
let varyX = nPos.x - oldPos.x;
let varyY = nPos.y - oldPos.y;
let animationState = '';
if (Math.abs(varyX) > Math.abs(varyY)) {
animationState = varyX > 0 ? "heroRight" : "heroLeft";
} else {
animationState = varyY > 0 ? "heroUp" : "heroDown";
}
if (animationState) this.setAnimationState(animationState);When the touch ends or is cancelled we stop the animation by playing an empty state.
onTouchEnd () {
// …
this.heroAnimation.play("");
},
onTouchCancel: function () {
// …
this.heroAnimation.play("");
},4. Add Physics Collision Between Hero and Walls
Create custom groups hero and wall and enable collision between them in the project settings. Add a BoxCollider to the hero node, disable rotation, and attach a RigidBody (type Dynamic ).
onLoad: function () {
let physicsManager = cc.director.getPhysicsManager();
physicsManager.enabled = true;
physicsManager.debugDrawFlags = true; // for debugging
physicsManager.gravity = cc.v2(0, 0);
this.rigidBody = this.getComponent(cc.RigidBody);
},Instead of directly setting x / y , we now modify the hero’s linear velocity based on the swipe direction:
if (Math.abs(varyX) > Math.abs(varyY)) {
this.rigidBody.linearVelocity = varyX > 0 ? cc.v2(this.speed, 0) : cc.v2(-this.speed, 0);
animationState = varyX > 0 ? "heroRight" : "heroLeft";
} else {
this.rigidBody.linearVelocity = varyY > 0 ? cc.v2(0, this.speed) : cc.v2(0, -this.speed);
animationState = varyY > 0 ? "heroUp" : "heroDown";
}For wall objects we add a static RigidBody and a PhysicsBoxCollider . When using a tiled map, we iterate over the wall layer, set each tile’s group to wall , add a static rigid body, and configure the collider size and offset based on the tile size.
properties: {
tiledMap: cc.TiledMap,
},
startGame: function () {
let wallLayer = this.tiledMap.getLayer("wall");
let wallLayerSize = wallLayer.getLayerSize();
for (let i = 0; i < wallLayerSize.width; i++) {
for (let j = 0; j < wallLayerSize.height; j++) {
let tiledWall = wallLayer.getTiledTileAt(i, j, true);
if (tiledWall.gid != 0) {
tiledWall.node.group = "wall";
let rigidBody = tiledWall.node.addComponent(cc.RigidBody);
rigidBody.type = cc.RigidBodyType.Static;
let collider = tiledWall.node.addComponent(cc.PhysicsBoxCollider);
let tiledWallSize = this.tiledMap.getTileSize();
collider.offset = cc.v2(tiledWallSize.width / 2, tiledWallSize.height / 2);
collider.size = tiledWallSize;
collider.apply();
}
}
}
},After enabling the physics manager and setting gravity to zero, the hero collides with walls and cannot pass through them, confirming that the physics‑based movement and collision system works correctly.
LOFTER Tech Team
Technical sharing and discussion from NetEase LOFTER Tech Team
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.